طريقة عمل المتصفِّحات

ما وراء كواليس متصفّحات الويب الحديثة

تمهيد

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

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

"بول آيريش"، مسؤول علاقات المطوّرين في Chrome

مقدمة

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

المتصفّحات التي سنتحدّث عنها

هناك خمسة متصفحات رئيسية تُستخدم على أجهزة الكمبيوتر المكتبي اليوم: Chrome وInternet Explorer وFirefox وSafari وOpera. على الأجهزة الجوّالة، المتصفّحات الرئيسية هي متصفّح Android وiPhone وOpera Mini وOpera Mobile وUC Browser ومتصفّحات Nokia S40/S60 وChrome، وجميعها تستند إلى WebKit باستثناء متصفّحات Opera. سأقدّم أمثلة من المتصفّحات المفتوحة المصدر Firefox وChrome وSafari (وهو مفتوح المصدر جزئيًا). وفقًا لإحصاءات StatCounter (اعتبارًا من يونيو 2013)، يشكّل Chrome وFirefox وSafari حوالي% 71 من استخدام المتصفّحات المتوافقة مع أجهزة الكمبيوتر المكتبي على مستوى العالم. وعلى الأجهزة الجوّالة، يشكّل متصفّح Android وiPhone وChrome حوالي% 54 من الاستخدام.

الوظيفة الرئيسية للمتصفّح

تتمثّل الوظيفة الرئيسية للمتصفّح في عرض مورد الويب الذي تختاره، وذلك من خلال طلبه من الخادم وعرضه في نافذة المتصفّح. عادةً ما يكون المورد مستند HTML، ولكن يمكن أن يكون أيضًا ملف PDF أو صورة أو نوعًا آخر من المحتوى. يحدّد المستخدم موقع المورد باستخدام معرّف الموارد المنتظم (URI).

يتم تحديد الطريقة التي يفسّر بها المتصفّح ملفات HTML ويعرضها في مواصفات HTML وCSS. تتم إدارة هذه المواصفات من قِبل منظمة W3C (رابطة الشبكة العالمية)، وهي منظمة المعايير للويب. لسنوات، كانت المتصفحات تلتزم بجزء فقط من المواصفات وتطوّر إضافاتها الخاصة. وقد تسبّب ذلك في حدوث مشاكل خطيرة في التوافق لمؤلفي الويب. اليوم، تتوافق معظم المتصفحات بشكل أو بآخر مع المواصفات.

تتشابه واجهات مستخدم المتصفّحات كثيرًا مع بعضها البعض. من بين عناصر واجهة المستخدم الشائعة:

  1. شريط العناوين لإدراج معرّف موارد منتظم (URI)
  2. زرّا "الرجوع" و"التقديم"
  3. خيارات الإشارات المرجعية
  4. زرّا إعادة التحميل والإيقاف لإعادة تحميل المستندات الحالية أو إيقاف تحميلها
  5. زر الصفحة الرئيسية الذي ينقلك إلى صفحتك الرئيسية

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

بنية تحتية عالية المستوى

المكوّنات الرئيسية للمتصفّح هي:

  1. واجهة المستخدم: تشمل شريط العناوين وزرَّي الرجوع والتقديم وقائمة الإشارات المرجعية وما إلى ذلك، أي كل جزء من شاشة المتصفّح باستثناء النافذة التي تظهر فيها الصفحة المطلوبة.
  2. محرّك المتصفّح: ينظّم الإجراءات بين واجهة المستخدم ومحرّك العرض.
  3. محرّك العرض: مسؤول عن عرض المحتوى المطلوب. على سبيل المثال، إذا كان المحتوى المطلوب هو HTML، يحلّل محرّك العرض HTML وCSS، ويعرض المحتوى الذي تم تحليله على الشاشة.
  4. الشبكات: لمكالمات الشبكة مثل طلبات HTTP، باستخدام عمليات تنفيذ مختلفة لمنصات مختلفة خلف واجهة مستقلة عن المنصة.
  5. الواجهة الخلفية لواجهة المستخدم: تُستخدَم لرسم التطبيقات المصغّرة الأساسية، مثل مربّعات التحرير والسرد والنوافذ. تعرض هذه الواجهة الخلفية واجهة عامة غير خاصة بالنظام الأساسي. وتستخدم في الخلفية طرق واجهة المستخدم الخاصة بنظام التشغيل.
  6. مترجم JavaScript: يُستخدَم لتحليل رمز JavaScript وتنفيذه.
  7. تخزين البيانات: هذه طبقة ثبات. قد يحتاج المتصفّح إلى حفظ جميع أنواع البيانات محليًا، مثل ملفات تعريف الارتباط. تتيح المتصفّحات أيضًا آليات تخزين مثل localStorage وIndexedDB وWebSQL وFileSystem.
مكوّنات المتصفّح
الشكل 1: مكوّنات المتصفّح

من المهمّ معرفة أنّ المتصفّحات، مثل Chrome، تشغّل نُسخًا متعددة من محرّك العرض: نسخة واحدة لكل علامة تبويب. يتم تشغيل كل علامة تبويب في عملية منفصلة.

محرّكات العرض

تتمثل مسؤولية محرك العرض في العرض، أي عرض المحتويات المطلوبة على شاشة المتصفح.

بشكل تلقائي، يمكن لمحرّك العرض عرض مستندات HTML وXML والصور. ويمكنه عرض أنواع أخرى من البيانات من خلال المكوّنات الإضافية أو الإضافات، مثل عرض مستندات PDF باستخدام مكوّن إضافي لعرض ملفات PDF. ومع ذلك، سنركز في هذا الفصل على حالة الاستخدام الرئيسية: عرض HTML والصور التي تم تنسيقها باستخدام CSS.

تستخدم المتصفحات المختلفة محركات عرض مختلفة: يستخدم Internet Explorer محرك Trident، ويستخدم Firefox محرك Gecko، ويستخدم Safari محرك WebKit. يستخدم Chrome وOpera (من الإصدار 15) محرّك Blink، وهو نسخة من WebKit.

‫WebKit هو محرّك عرض مفتوح المصدر بدأ كمحرّك لنظام التشغيل Linux وعدّلته شركة Apple ليتوافق مع نظامَي التشغيل Mac وWindows.

التدفق الرئيسي

سيبدأ محرّك العرض في الحصول على محتوى المستند المطلوب من طبقة الشبكة. سيتم ذلك عادةً في أجزاء بحجم 8 كيلوبايت.

بعد ذلك، إليك المسار الأساسي لمحرك العرض:

الخطوات الأساسية لمحرك العرض
الشكل 2: المسار الأساسي لمحرّك العرض

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

تحتوي شجرة العرض على مستطيلات ذات سمات مرئية مثل اللون والأبعاد. تكون المستطيلات بالترتيب الصحيح ليتم عرضها على الشاشة.

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

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

أمثلة على المسار الرئيسي

مسار WebKit الرئيسي
الشكل 3: المسار الرئيسي في WebKit
المسار الرئيسي لمحرّك العرض Gecko من Mozilla
الشكل 4: المسار الرئيسي في محرّك العرض Gecko من Mozilla

من الشكلين 3 و4، يمكنك ملاحظة أنّه على الرغم من أنّ WebKit وGecko يستخدمان مصطلحات مختلفة قليلاً، فإنّ العملية هي نفسها في الأساس.

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

التحليل - عام

بما أنّ التحليل هو عملية مهمة جدًا ضمن محرّك العرض، سنتعمّق فيه أكثر. لنبدأ بمقدمة بسيطة عن التحليل اللغوي.

يعني تحليل مستند ترجمته إلى بنية يمكن للرمز البرمجي استخدامها. وتكون نتيجة التحليل عادةً عبارة عن شجرة من العُقَد التي تمثّل بنية المستند. ويُطلق على ذلك اسم شجرة التحليل أو شجرة البنية.

على سبيل المثال، قد يؤدي تحليل التعبير 2 + 3 - 1 إلى عرض هذه الشجرة:

عقدة شجرة التعبير الرياضي
الشكل 5: عقدة شجرة التعبير الحسابي

القواعد اللغوية

يعتمد التحليل على قواعد البنية التي يلتزم بها المستند: اللغة أو التنسيق الذي تمت كتابته به. يجب أن يكون لكل تنسيق يمكنك تحليله قواعد نحوية حتمية تتكون من مفردات وقواعد نحوية. ويُطلق على هذه القواعد اسم قواعد اللغة الخالية من السياق، وهي لا تنطبق على اللغات البشرية، وبالتالي لا يمكن تحليلها باستخدام تقنيات التحليل التقليدية.

محلل لغوي - تركيبة Lexer

يمكن تقسيم التحليل إلى عمليتين فرعيتين: التحليل المعجمي والتحليل النحوي.

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

تحليل البنية هو تطبيق قواعد بنية اللغة.

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

يعرف المحلل اللغوي كيفية تجريد الأحرف غير ذات الصلة مثل المسافات البيضاء وفواصل الأسطر.

من المستند المصدر إلى أشجار التحليل
الشكل 6: من المستند المصدر إلى أشجار التحليل

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

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

الترجمة

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

مسار التحويل البرمجي
الشكل 7: عملية التجميع

مثال على التحليل

في الشكل 5، أنشأنا شجرة تحليل من تعبير رياضي. لنجرّب تحديد لغة رياضية بسيطة والاطّلاع على عملية التحليل اللغوي.

البنية:

  1. تتألف الوحدات الأساسية لبناء الجملة في اللغة من التعبيرات والمصطلحات والعمليات.
  2. يمكن أن تتضمّن لغتنا أي عدد من التعبيرات.
  3. يتم تعريف التعبير على أنه "حد" متبوع بـ "عملية" متبوع بحد آخر
  4. العملية هي رمز زائد أو رمز ناقص
  5. الحدّ هو رمز مميّز لعدد صحيح أو تعبير

لنحلّل الإدخال 2 + 3 - 1.

أول سلسلة فرعية تطابق قاعدة هي 2: وفقًا للقاعدة رقم 5، هي عبارة. المطابقة الثانية هي 2 + 3: يتطابق هذا مع القاعدة الثالثة: عبارة متبوعة بعملية متبوعة بعبارة أخرى. لن يتم العثور على المطابقة التالية إلا في نهاية الإدخال. ‫2 + 3 - 1 هو تعبير لأنّنا نعرف أنّ 2 + 3 هو حدّ، وبالتالي لدينا حدّ متبوع بعملية متبوعة بحدّ آخر. لن يتطابق 2 + + مع أي قاعدة، وبالتالي فهو إدخال غير صالح.

تعريفات رسمية للمفردات وبناء الجملة

يتم التعبير عن المفردات عادةً من خلال التعبيرات العادية.

على سبيل المثال، سيتم تحديد لغتنا على النحو التالي:

INTEGER: 0|[1-9][0-9]*
PLUS: +
MINUS: -

كما ترى، يتم تحديد الأعداد الصحيحة من خلال تعبير عادي.

يتم عادةً تحديد البنية بتنسيق يُعرف باسم BNF. سيتم تعريف لغتنا على النحو التالي:

expression :=  term  operation  term
operation :=  PLUS | MINUS
term := INTEGER | expression

قلنا إنّ اللغة يمكن تحليلها بواسطة المحللات اللغوية العادية إذا كانت قواعدها النحوية خالية من السياق. التعريف البديهي للقواعد النحوية الخالية من السياق هو القواعد التي يمكن التعبير عنها بالكامل في BNF. للحصول على تعريف رسمي، يمكنك الاطّلاع على مقالة ويكيبيديا حول القواعد النحوية الخالية من السياق.

أنواع المحلّلات اللغوية

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

لنرى كيف سيحلّل نوعا المحلّلات اللغوية مثالنا.

سيبدأ المحلل اللغوي من أعلى إلى أسفل من القاعدة ذات المستوى الأعلى: سيحدد 2 + 3 كتعبير. بعد ذلك، سيتم تحديد 2 + 3 - 1 كتعبير (تتطوّر عملية تحديد التعبير، وتطابق القواعد الأخرى، ولكن نقطة البداية هي القاعدة ذات المستوى الأعلى).

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

تكديس الإدخال
2 + 3 - 1
عبارة + 3 - 1
عملية على العبارات 3 - 1
تعبير - 1
عملية التعبير 1
تعبير -

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

إنشاء المحلِّلات اللغوية تلقائيًا

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

يستخدم WebKit منشئَين معروفَين للمحللات اللغوية: Flex لإنشاء محلل معجمي وBison لإنشاء محلل لغوي (قد تصادفهما باسمَي Lex وYacc). إدخال Flex هو ملف يحتوي على تعريفات التعبير العادي للرموز المميزة. تتمثل مدخلات Bison في قواعد بناء الجملة اللغوية بتنسيق BNF.

محلّل HTML

تتمثل مهمة محلل HTML في تحليل ترميز HTML إلى شجرة تحليل.

قواعد HTML

يتم تحديد مفردات HTML وبناء الجملة في المواصفات التي أنشأتها منظمة W3C.

كما رأينا في مقدمة التحليل، يمكن تعريف بنية القواعد رسميًا باستخدام تنسيقات مثل BNF.

للأسف، لا تنطبق جميع مواضيع المحلّل اللغوي التقليدية على HTML (لم أذكرها لمجرد التسلية، بل سيتم استخدامها في تحليل CSS وJavaScript). لا يمكن تعريف HTML بسهولة من خلال قواعد نحوية خالية من السياق تحتاجها المحللات اللغوية.

هناك تنسيق رسمي لتعريف HTML - DTD (تعريف نوع المستند) - ولكنه ليس قواعد نحوية خالية من السياق.

قد يبدو هذا غريبًا للوهلة الأولى، لأنّ HTML قريب إلى حدّ ما من XML. هناك العديد من المحلّلات اللغوية المتاحة لملفات XML. هناك صيغة XML من HTML، وهي XHTML، فما هو الفرق الكبير بينهما؟

يكمن الفرق في أنّ نهج HTML أكثر "تساهلاً"، فهو يتيح لك حذف علامات معيّنة (تتم إضافتها بعد ذلك ضمنيًا)، أو حذف علامات البداية أو النهاية في بعض الأحيان، وما إلى ذلك. بشكل عام، إنّها بنية "مرنة"، على عكس بنية XML الصارمة والمتطلّبة.

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

HTML DTD

تعريف HTML يكون بتنسيق DTD. يُستخدَم هذا التنسيق لتحديد لغات عائلة SGML. يحتوي التنسيق على تعريفات لجميع العناصر المسموح بها وسماتها وتسلسلها الهرمي. كما رأينا سابقًا، لا يشكّل HTML DTD قواعد نحوية خالية من السياق.

هناك بعض الاختلافات في تعريف نوع المستند (DTD). يتوافق وضع التدقيق الصارم مع المواصفات فقط، ولكن تتضمّن الأوضاع الأخرى دعمًا للترميز الذي استخدمته المتصفّحات في الماضي. والغرض من ذلك هو التوافق مع الأنظمة القديمة للمحتوى الأقدم. يتوفّر تعريف نوع المستند الصارم الحالي على الرابط التالي: www.w3.org/TR/html4/strict.dtd

DOM

شجرة الإخراج ("شجرة التحليل") هي شجرة من عناصر DOM وعُقد السمات. ‫DOM هو اختصار لـ Document Object Model (نموذج العناصر في المستند). وهو تمثيل الكائن لمستند HTML وواجهة عناصر HTML للعالم الخارجي مثل JavaScript.

جذر الشجرة هو كائن "المستند".

يرتبط نموذج DOM بعلاقة مباشرة تقريبًا مع الترميز. على سبيل المثال:

<html>
  <body>
    <p>
      Hello World
    </p>
    <div> <img src="example.png"/></div>
  </body>
</html>

سيتم ترجمة هذا الترميز إلى شجرة نموذج العناصر في المستند التالية:

شجرة نموذج العناصر في المستند للترميز في المثال
الشكل 8: شجرة نموذج العناصر في المستند للترميز في المثال

مثل HTML، يتم تحديد DOM من قِبل منظمة W3C. يُرجى الاطّلاع على www.w3.org/DOM/DOMTR. وهي مواصفات عامة لمعالجة المستندات. تصف وحدة معيّنة عناصر HTML المحدّدة. يمكن العثور على تعريفات HTML هنا: www.w3.org/TR/2003/REC-DOM-Level-2-HTML-20030109/idl-definitions.html.

عندما أقول إنّ الشجرة تحتوي على عُقد DOM، أعني أنّ الشجرة تتكوّن من عناصر تنفّذ إحدى واجهات DOM. تستخدم المتصفّحات عمليات تنفيذ ملموسة تتضمّن سمات أخرى يستخدمها المتصفّح داخليًا.

خوارزمية التحليل

كما رأينا في الأقسام السابقة، لا يمكن تحليل HTML باستخدام المحللات اللغوية العادية من الأعلى إلى الأسفل أو من الأسفل إلى الأعلى.

في ما يلي الأسباب:

  1. طبيعة اللغة المتسامحة.
  2. حقيقة أنّ المتصفّحات لديها قدرة تقليدية على تحمّل الأخطاء لدعم الحالات المعروفة لترميز HTML غير الصالح.
  3. عملية التحليل قابلة لإعادة الدخول. بالنسبة إلى اللغات الأخرى، لا يتغيّر المصدر أثناء التحليل، ولكن في HTML، يمكن للرمز الديناميكي (مثل عناصر النصوص البرمجية التي تحتوي على طلبات document.write()) إضافة رموز مميّزة إضافية، وبالتالي تعدّل عملية التحليل الإدخال فعليًا.

بسبب عدم القدرة على استخدام تقنيات التحليل العادية، تنشئ المتصفحات محللات مخصصة لتحليل HTML.

يتم وصف خوارزمية التحليل بالتفصيل في مواصفات HTML5. تتكون الخوارزمية من مرحلتين: الترميز وبناء الشجرة.

الترميز هو التحليل المعجمي، وتحليل المدخلات إلى رموز مميزة. ومن بين رموز HTML المميّزة علامات البداية وعلامات النهاية وأسماء السمات وقيم السمات.

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

سير عملية تحليل HTML (مأخوذ من مواصفات HTML5)
الشكل 9: مسار تحليل HTML (مأخوذ من مواصفات HTML5)

خوارزمية إنشاء الرموز المميّزة

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

مثال أساسي - تحويل HTML التالي إلى رموز مميزة:

<html>
  <body>
    Hello world
  </body>
</html>

الحالة الأولية هي "حالة البيانات". عند العثور على الحرف <، يتم تغيير الحالة إلى "حالة فتح العلامة". يؤدي استخدام حرف a-z إلى إنشاء "رمز مميّز لعلامة البداية"، ويتم تغيير الحالة إلى "حالة اسم العلامة". ونبقى في هذه الحالة إلى أن يتم استهلاك الحرف >. يتم إلحاق كل حرف باسم الرمز المميّز الجديد. في حالتنا، الرمز المميّز الذي تم إنشاؤه هو رمز مميّز من النوع html.

عند الوصول إلى علامة >، يتم إصدار الرمز المميّز الحالي وتتغيّر الحالة مرة أخرى إلى "حالة البيانات". سيتم التعامل مع علامة <body> باتّباع الخطوات نفسها. حتى الآن، تم إصدار علامتَي html وbody. لقد عدنا الآن إلى "حالة البيانات". سيؤدي استهلاك الحرف H من Hello world إلى إنشاء رمز مميّز للحرف وإصداره، ويستمر ذلك إلى أن يتم الوصول إلى < من </body>. سنُصدر رمزًا مميزًا لكل حرف من Hello world.

لقد عدنا الآن إلى "حالة فتح العلامة". سيؤدي استخدام الإدخال التالي / إلى إنشاء end tag token والانتقال إلى "حالة اسم العلامة". نظل في هذه الحالة إلى أن نصل إلى >، ثم يتم إصدار رمز مميّز جديد للعلامة ونعود إلى "حالة البيانات". سيتم التعامل مع الإدخال </html> مثل الحالة السابقة.

تحويل مثال الإدخال إلى رموز مميزة
الشكل 10: تقسيم مثال الإدخال إلى رموز مميّزة

خوارزمية إنشاء الشجرة

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

لنلقِ نظرة على عملية إنشاء الشجرة لمثال الإدخال:

<html>
  <body>
    Hello world
  </body>
</html>

تكون المدخلات في مرحلة إنشاء الشجرة عبارة عن تسلسل من الرموز المميّزة من مرحلة الترميز. الوضع الأول هو "الوضع الأولي". سيؤدي تلقّي الرمز المميّز "html" إلى الانتقال إلى وضع "قبل html" وإعادة معالجة الرمز المميّز في هذا الوضع. سيؤدي ذلك إلى إنشاء عنصر HTMLHtmlElement، والذي سيتم إلحاقه بعنصر Document الجذر.

سيتم تغيير الحالة إلى "قبل الرأس". بعد ذلك، يتم تلقّي الرمز المميّز "body". سيتم إنشاء HTMLHeadElement ضمنيًا على الرغم من عدم توفّر رمز "head"، وستتم إضافته إلى الشجرة.

ننتقل الآن إلى وضع "في الرأس" ثم إلى "بعد الرأس". تتم إعادة معالجة الرمز المميّز للنص، ويتم إنشاء عنصر HTMLBodyElement وإدراجه، ويتم نقل الوضع إلى "في النص".

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

سيؤدي تلقّي الرمز المميّز لنهاية النص إلى الانتقال إلى وضع "بعد النص". سنتلقّى الآن علامة النهاية في HTML التي ستنقلنا إلى وضع "بعد بعد النص". وعند تلقّي رمز نهاية الملف، ستنتهي عملية التحليل.

بنية شجرة لمثال HTML
الشكل 11: إنشاء شجرة من مثال html

الإجراءات التي يتم تنفيذها عند انتهاء التحليل

في هذه المرحلة، سيضع المتصفّح علامة على المستند باعتباره تفاعليًا ويبدأ في تحليل النصوص البرمجية التي تكون في وضع "مؤجَّل"، أي تلك التي يجب تنفيذها بعد تحليل المستند. سيتم بعد ذلك ضبط حالة المستند على "مكتمل" وسيتم تنشيط حدث "تحميل".

يمكنك الاطّلاع على الخوارزميات الكاملة لإنشاء الرموز المميزة وبنية العرض التدرجي في مواصفات HTML5.

تسامح المتصفحات مع الأخطاء

لن تظهر لك رسالة الخطأ "بنية غير صالحة" على صفحة HTML. وتعمل المتصفحات على إصلاح أي محتوى غير صالح والمتابعة.

على سبيل المثال، إليك رمز HTML التالي:

<html>
  <mytag>
  </mytag>
  <div>
  <p>
  </div>
    Really lousy HTML
  </p>
</html>

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

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

تحدّد مواصفات HTML5 بعض هذه المتطلبات. (يلخّص محرّك WebKit ذلك بشكل جيد في التعليق في بداية فئة المحلل اللغوي لـ HTML).

يحلّل المحلل اللغوي الإدخال الذي تم تقسيمه إلى رموز مميزة في المستند، ما يؤدي إلى إنشاء شجرة المستند. إذا كان المستند منسقًا بشكل صحيح، يكون تحليله سهلاً.

للأسف، علينا التعامل مع العديد من مستندات HTML التي لم يتم إنشاؤها بشكل صحيح، لذا يجب أن يكون المحلل اللغوي متسامحًا مع الأخطاء.

علينا معالجة حالات الخطأ التالية على الأقل:

  1. العنصر الذي تتم إضافته محظور صراحةً داخل بعض العلامات الخارجية. في هذه الحالة، يجب إغلاق جميع العلامات حتى العلامة التي تمنع العنصر، وإضافتها بعد ذلك.
  2. لا يُسمح لنا بإضافة العنصر مباشرةً. قد يكون الشخص الذي يكتب المستند قد نسي بعض العلامات بينهما (أو أن العلامة بينهما اختيارية). قد يكون هذا هو الحال مع العلامات التالية: HTML HEAD BODY TBODY TR TD LI (هل نسيت أيًا منها؟).
  3. نريد إضافة عنصر كتلة داخل عنصر مضمّن. إغلاق جميع العناصر المضمّنة حتى عنصر الكتلة الأعلى التالي
  4. إذا لم يساعدك ذلك، أغلِق العناصر إلى أن يُسمح لنا بإضافة العنصر، أو تجاهَل العلامة.

في ما يلي بعض الأمثلة على تسامح WebKit مع الأخطاء:

‫‎</br> بدلاً من ‎<br>

تستخدم بعض المواقع الإلكترونية </br> بدلاً من <br>. للتوافق مع IE وFirefox، يتعامل WebKit مع هذا على أنه <br>.

الرمز:

if (t->isCloseTag(brTag) && m_document->inCompatMode()) {
     reportError(MalformedBRError);
     t->beginTag = true;
}

يُرجى العِلم أنّ معالجة الأخطاء تتم داخليًا، ولن تظهر للمستخدم.

طاولة ضالة

الجدول الشارد هو جدول داخل جدول آخر، ولكن ليس داخل خلية جدول.

على سبيل المثال:

<table>
  <table>
    <tr><td>inner table</td></tr>
  </table>
  <tr><td>outer table</td></tr>
</table>

سيغيّر WebKit التسلسل الهرمي إلى جدولَين شقيقَين:

<table>
  <tr><td>outer table</td></tr>
</table>
<table>
  <tr><td>inner table</td></tr>
</table>

الرمز:

if (m_inStrayTableContent && localName == tableTag)
        popBlock(tableTag);

يستخدم WebKit حزمة لمحتوى العنصر الحالي: سيخرج الجدول الداخلي من حزمة الجدول الخارجي. ستكون الجداول الآن متساوية.

عناصر النماذج المدمجة

في حال وضع المستخدم نموذجًا داخل نموذج آخر، يتم تجاهل النموذج الثاني.

الرمز:

if (!m_currentFormElement) {
        m_currentFormElement = new HTMLFormElement(formTag,    m_document);
}

التسلسل الهرمي للعلامات عميق جدًا

التعليق يتحدث عن نفسه.

bool HTMLParser::allowNestedRedundantTag(const AtomicString& tagName)
{

unsigned i = 0;
for (HTMLStackElem* curr = m_blockStack;
         i < cMaxRedundantTagDepth && curr && curr->tagName == tagName;
     curr = curr->next, i++) { }
return i != cMaxRedundantTagDepth;
}

علامات الإغلاق html أو body في غير مكانها

مرة أخرى، التعليق يتحدث عن نفسه.

if (t->tagName == htmlTag || t->tagName == bodyTag )
        return;

لذا، على مؤلفي الويب الحذر، ما لم يرغبوا في الظهور كمثال في مقتطف رمز التسامح مع الأخطاء في WebKit، عليهم كتابة HTML بشكل جيد.

تحليل CSS

هل تتذكر مفاهيم التحليل اللغوي في المقدّمة؟ على عكس HTML، فإنّ CSS هي لغة مستقلة عن السياق ويمكن تحليلها باستخدام أنواع المحلّلات اللغوية الموضّحة في المقدّمة. في الواقع، تحدّد مواصفات CSS القواعد النحوية والمعجمية للغة CSS.

لنلقِ نظرة على بعض الأمثلة:

يتم تحديد القواعد النحوية المعجمية (المفردات) من خلال التعبيرات العادية لكل رمز مميّز:

comment   \/\*[^*]*\*+([^/*][^*]*\*+)*\/
num       [0-9]+|[0-9]*"."[0-9]+
nonascii  [\200-\377]
nmstart   [_a-z]|{nonascii}|{escape}
nmchar    [_a-z0-9-]|{nonascii}|{escape}
name      {nmchar}+
ident     {nmstart}{nmchar}*

"ident" هو اختصار لكلمة "identifier" (معرّف)، مثل اسم الفئة. "name" هو معرّف عنصر (يُشار إليه بـ "#")

يتم وصف قواعد البنية في BNF.

ruleset
  : selector [ ',' S* selector ]*
    '{' S* declaration [ ';' S* declaration ]* '}' S*
  ;
selector
  : simple_selector [ combinator selector | S+ [ combinator? selector ]? ]?
  ;
simple_selector
  : element_name [ HASH | class | attrib | pseudo ]*
  | [ HASH | class | attrib | pseudo ]+
  ;
class
  : '.' IDENT
  ;
element_name
  : IDENT | '*'
  ;
attrib
  : '[' S* IDENT S* [ [ '=' | INCLUDES | DASHMATCH ] S*
    [ IDENT | STRING ] S* ] ']'
  ;
pseudo
  : ':' [ IDENT | FUNCTION S* [IDENT S*] ')' ]
  ;

الشرح:

مجموعة القواعد هي هذه البنية:

div.error, a.error {
  color:red;
  font-weight:bold;
}

‫div.error وa.error هما أداتا اختيار. يحتوي الجزء داخل الأقواس المتعرجة على القواعد التي تطبّقها مجموعة القواعد هذه. يتم تحديد هذه البنية رسميًا في هذا التعريف:

ruleset
  : selector [ ',' S* selector ]*
    '{' S* declaration [ ';' S* declaration ]* '}' S*
  ;

وهذا يعني أنّ مجموعة القواعد هي أداة اختيار أو عدد من أدوات الاختيار المفصولة بفاصلة ومسافات (يرمز الحرف S إلى المسافة البيضاء). تحتوي مجموعة القواعد على أقواس معقوفة وبداخلها بيان أو اختياريًا عدد من البيانات مفصولة بفاصلة منقوطة. سيتم تحديد "declaration" و "selector" في تعريفات BNF التالية.

محلّل لغوي لـ WebKit CSS

يستخدم WebKit منشئي المحلّلات اللغوية Flex وBison لإنشاء محلّلات لغوية تلقائيًا من ملفات قواعد CSS. كما تتذكر من مقدمة المحلل اللغوي، ينشئ Bison محللًا لغويًا من الأسفل إلى الأعلى. يستخدم Firefox محللاً لغويًا من الأعلى إلى الأسفل مكتوبًا يدويًا. في كلتا الحالتين، يتم تحليل كل ملف CSS إلى كائن StyleSheet. يحتوي كل عنصر على قواعد CSS. تحتوي كائنات قواعد CSS على كائنات أدوات الاختيار والإعلانات وكائنات أخرى تتوافق مع قواعد CSS.

تحليل CSS
الشكل 12: تحليل CSS

ترتيب معالجة النصوص البرمجية وأوراق الأنماط

النصوص البرمجية

نموذج الويب متزامن. يتوقّع المؤلفون أن يتم تحليل النصوص البرمجية وتنفيذها فور وصول المحلل اللغوي إلى علامة <script>. يتوقف تحليل المستند مؤقتًا إلى أن يتم تنفيذ النص البرمجي. إذا كان النص البرمجي خارجيًا، يجب أولاً جلب المورد من الشبكة، ويتم ذلك أيضًا بشكل متزامن، ويتوقف التحليل مؤقتًا إلى أن يتم جلب المورد. كان هذا هو النموذج لسنوات عديدة، وهو محدّد أيضًا في مواصفات HTML4 و5. يمكن للمؤلفين إضافة السمة "defer" إلى نص برمجي، وفي هذه الحالة لن يتم إيقاف تحليل المستند وسيتم تنفيذه بعد تحليل المستند. يضيف HTML5 خيارًا لوضع علامة على النص البرمجي على أنّه غير متزامن، وبالتالي سيتم تحليله وتنفيذه من خلال سلسلة محادثات مختلفة.

التحليل التخميني

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

أوراق الأنماط

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

إنشاء شجرة العرض

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

يُطلق Firefox على العناصر في شجرة العرض اسم "الإطارات". يستخدم WebKit مصطلح "محرّك العرض" أو "كائن العرض".

يعرف العارض كيفية تخطيط ورسم نفسه وعناصره الفرعية.

يحتوي صنف RenderObject في WebKit، وهو الصنف الأساسي لأدوات العرض، على التعريف التالي:

class RenderObject{
  virtual void layout();
  virtual void paint(PaintInfo);
  virtual void rect repaintRect();
  Node* node;  //the DOM node
  RenderStyle* style;  // the computed style
  RenderLayer* containgLayer; //the containing z-index layer
}

يمثّل كل عارض مساحة مستطيلة تتوافق عادةً مع مربّع CSS الخاص بالعقدة، كما هو موضّح في مواصفات CSS2. ويتضمّن معلومات هندسية مثل العرض والارتفاع والموضع.

يتأثر نوع المربّع بقيمة "display" لسمة النمط ذات الصلة بالعقدة (راجِع قسم احتساب النمط). في ما يلي رمز WebKit لتحديد نوع المعالج الذي يجب إنشاؤه لعقدة DOM، وفقًا لسمة العرض:

RenderObject* RenderObject::createObject(Node* node, RenderStyle* style)
{
    Document* doc = node->document();
    RenderArena* arena = doc->renderArena();
    ...
    RenderObject* o = 0;

    switch (style->display()) {
        case NONE:
            break;
        case INLINE:
            o = new (arena) RenderInline(node);
            break;
        case BLOCK:
            o = new (arena) RenderBlock(node);
            break;
        case INLINE_BLOCK:
            o = new (arena) RenderBlock(node);
            break;
        case LIST_ITEM:
            o = new (arena) RenderListItem(node);
            break;
       ...
    }

    return o;
}

يتم أيضًا أخذ نوع العنصر في الاعتبار: على سبيل المثال، تحتوي عناصر التحكّم في النماذج والجداول على إطارات خاصة.

في WebKit، إذا أراد عنصر إنشاء عارض خاص، سيتجاوز طريقة createRenderer(). تشير أدوات العرض إلى عناصر الأنماط التي تحتوي على معلومات غير هندسية.

علاقة شجرة العرض بشجرة نموذج العناصر في المستند

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

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

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

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

شجرة العرض وشجرة نموذج العناصر في المستند (DOM) المقابلة
الشكل 13: شجرة العرض وشجرة نموذج العناصر في المستند المقابلة "إطار العرض" هو الكتلة الأولية التي تحتوي على المحتوى. في WebKit، سيكون عنصر "RenderView"

سير العمل لإنشاء الشجرة

في Firefox، يتم تسجيل العرض التقديمي كمستمع لتحديثات DOM. يفوّض العرض التقديمي إنشاء الإطار إلى FrameConstructor ويحلّ المُنشئ الأسلوب (راجِع حساب الأسلوب) وينشئ إطارًا.

في WebKit، تُسمى عملية حل النمط وإنشاء عارض "المرفق". تحتوي كل عقدة DOM على طريقة "إرفاق". يكون الإرفاق متزامنًا، ويؤدي إدراج العقدة في شجرة نموذج العناصر في المستند إلى استدعاء طريقة "إرفاق" العقدة الجديدة.

تؤدي معالجة علامتَي html وbody إلى إنشاء جذر شجرة العرض. يتوافق عنصر العرض الجذر مع ما تسميه مواصفات CSS الكتلة التي تحتوي على: الكتلة العلوية التي تحتوي على جميع الكتل الأخرى. وأبعادها هي إطار العرض: أبعاد مساحة عرض نافذة المتصفّح. يُطلق عليه Firefox اسم ViewPortFrame ويُطلق عليه WebKit اسم RenderView. هذا هو عنصر العرض الذي يشير إليه المستند. ويتم إنشاء بقية الشجرة كإدراج عُقد DOM.

يُرجى الاطّلاع على مواصفات CSS2 في نموذج المعالجة.

احتساب الأنماط

يتطلب إنشاء شجرة العرض حساب الخصائص المرئية لكل عنصر عرض. ويتم ذلك من خلال احتساب سمات النمط لكل عنصر.

يتضمّن النمط أوراق أنماط من مصادر مختلفة وعناصر أنماط مضمّنة وسمات مرئية في HTML (مثل السمة "bgcolor").تتم ترجمة السمة الأخيرة إلى سمات نمط CSS مطابقة.

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

تؤدي عملية احتساب الأنماط إلى بعض الصعوبات:

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

    على سبيل المثال، أداة الاختيار المركّبة هذه:

    div div div div{
    ...
    }
    

    يعني ذلك أنّ القواعد تنطبق على عنصر <div> الذي يمثّل عنصرًا فرعيًا لـ 3 عناصر div. لنفترض أنّك تريد التحقّق مما إذا كانت القاعدة تنطبق على عنصر <div> معيّن. تختار مسارًا معينًا أعلى الشجرة للتحقق. قد تحتاج إلى الانتقال إلى أعلى شجرة العُقد لمعرفة أنّ هناك عنصرَي div فقط وأنّ القاعدة لا تنطبق. بعد ذلك، عليك تجربة مسارات أخرى في الشجرة.

  3. يتضمن تطبيق القواعد قواعد متتالية معقدة للغاية تحدد التسلسل الهرمي للقواعد.

لنرى كيف تواجه المتصفحات هذه المشاكل:

مشاركة بيانات الأنماط

تشير عُقد WebKit إلى عناصر الأنماط (RenderStyle). يمكن مشاركة هذه العناصر بواسطة العُقد في بعض الحالات. تكون العُقد أشقاء أو أبناء عمومة، و:

  1. يجب أن تكون العناصر في حالة الماوس نفسها (على سبيل المثال، لا يمكن أن يكون أحدها في حالة :hover بينما الآخر ليس كذلك)
  2. يجب ألا يحتوي أي من العنصرَين على معرّف
  3. يجب أن تتطابق أسماء العلامات
  4. يجب أن تتطابق سمات الفئة
  5. يجب أن تكون مجموعة السمات المرتبطة متطابقة
  6. يجب أن تتطابق حالات الروابط
  7. يجب أن تتطابق حالات التركيز
  8. يجب ألا يتأثر أي من العنصرين بأدوات اختيار السمات، حيث يتم تعريف التأثر على أنه وجود أي تطابق لأداة اختيار يستخدم أداة اختيار سمة في أي موضع داخل أداة الاختيار على الإطلاق
  9. يجب ألا تكون هناك سمة نمط مضمّنة في العناصر
  10. يجب عدم استخدام أي محدّدات عناصر شقيقة على الإطلاق. يستخدم WebCore ببساطة مفتاحًا عامًا عند العثور على أي محدّد شقيق، ويوقف مشاركة الأنماط للمستند بأكمله عند توفّرها. ويشمل ذلك أداة الاختيار + وأدوات الاختيار مثل :first-child و :last-child.

شجرة قواعد Firefox

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

شجرة سياق نمط Firefox
الشكل 14: شجرة سياق نمط Firefox

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

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

الفكرة هي رؤية مسارات الشجرة ككلمات في معجم. لنفترض أنّنا احتسبنا شجرة القواعد هذه:

شجرة القواعد المحسوبة
الشكل 15: شجرة القواعد المحسوبة

لنفترض أنّنا بحاجة إلى مطابقة القواعد لعنصر آخر في شجرة المحتوى، وتبيّن لنا أنّ القواعد المطابقة (بالترتيب الصحيح) هي B-E-I. لدينا هذا المسار بالفعل في الشجرة لأننا قمنا بالفعل بحساب المسار A-B-E-I-L. سيكون لدينا الآن عمل أقل للقيام به.

لنرى كيف توفّر لنا الشجرة الوقت والجهد.

التقسيم إلى هياكل

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

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

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

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

إذا عثرنا على تعريفات جزئية، ننتقل إلى أعلى الشجرة حتى يتم ملء البنية.

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

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

في حال كان للعنصر عنصر شقيق يشير إلى عقدة الشجرة نفسها، يمكن مشاركة سياق النمط بالكامل بينهما.

لنلقِ نظرة على مثال: لنفترض أنّ لدينا رمز HTML التالي

<html>
  <body>
    <div class="err" id="div1">
      <p>
        this is a <span class="big"> big error </span>
        this is also a
        <span class="big"> very  big  error</span> error
      </p>
    </div>
    <div class="err" id="div2">another error</div>
  </body>
</html>

والقواعد التالية:

div {margin: 5px; color:black}
.err {color:red}
.big {margin-top:3px}
div span {margin-bottom:4px}
#div1 {color:blue}
#div2 {color:green}

لتبسيط الأمور، لنفترض أنّنا بحاجة إلى ملء بنيتَين فقط: بنية اللون وبنية الهامش. تحتوي بنية اللون على عضو واحد فقط: اللون تحتوي بنية الهامش على الجوانب الأربعة.

ستبدو شجرة القواعد الناتجة على النحو التالي (يتم وضع علامة على العُقد باسم العقدة: رقم القاعدة التي تشير إليها):

شجرة القواعد
الشكل 16: شجرة القواعد

ستبدو شجرة السياق على النحو التالي (اسم العقدة: عقدة القاعدة التي تشير إليها):

شجرة السياق
الشكل 17: شجرة السياق

لنفترض أنّنا حلّلنا لغة HTML ووصلنا إلى علامة <div> الثانية. نحتاج إلى إنشاء سياق نمط لهذه العقدة وملء بنيات النمط الخاصة بها.

سنطابق القواعد ونكتشف أنّ القواعد المطابقة لـ "<div>" هي 1 و2 و6. هذا يعني أنّ هناك مسارًا حاليًا في الشجرة يمكن أن يستخدمه العنصر، وعلينا فقط إضافة عقدة أخرى إليه للقاعدة 6 (العقدة F في شجرة القواعد).

سننشئ سياق نمط ونضعه في شجرة السياق. سيشير سياق النمط الجديد إلى العقدة F في شجرة القواعد.

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

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

العمل على عنصر <span> الثاني أسهل. سنطابق القواعد ونستنتج أنّها تشير إلى القاعدة G، مثل الجزء السابق. بما أنّ لدينا عناصر شقيقة تشير إلى العقدة نفسها، يمكننا مشاركة سياق النمط بالكامل والإشارة فقط إلى سياق العنصر span السابق.

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

على سبيل المثال، إذا أضفنا قواعد للخطوط في فقرة:

p {font-family: Verdana; font size: 10px; font-weight: bold}

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

في WebKit، الذي لا يحتوي على شجرة قواعد، يتم اجتياز التعريفات المطابقة أربع مرات. يتم أولاً تطبيق السمات غير المهمة ذات الأولوية العالية (السمات التي يجب تطبيقها أولاً لأنّ السمات الأخرى تعتمد عليها، مثل سمة display)، ثم السمات المهمة ذات الأولوية العالية، ثم السمات غير المهمة ذات الأولوية العادية، ثم القواعد المهمة ذات الأولوية العادية. وهذا يعني أنّ السمات التي تظهر عدة مرات سيتم حلّها وفقًا لترتيب التتالي الصحيح. ويفوز آخر لاعب.

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

التلاعب بالقواعد لتسهيل المباراة

هناك عدة مصادر لقواعد الأنماط:

  1. قواعد CSS، سواء في أوراق الأنماط الخارجية أو في عناصر الأنماطcss p {color: blue}
  2. سمات الأنماط المضمَّنة، مثل html <p style="color: blue" />
  3. سمات HTML المرئية (التي يتم ربطها بقواعد الأنماط ذات الصلة) html <p bgcolor="blue" /> يمكن مطابقة السمتَين الأخيرتَين بسهولة مع العنصر لأنّه يملك سمات الأنماط ويمكن ربط سمات HTML باستخدام العنصر كمفتاح.

كما ذكرنا سابقًا في المشكلة رقم 2، يمكن أن تكون مطابقة قواعد CSS أكثر صعوبة. لحلّ هذه الصعوبة، يتم التلاعب بالقواعد لتسهيل الوصول.

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

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

لنلقِ نظرة على قواعد الأنماط التالية:

p.error {color: red}
#messageDiv {height: 50px}
div {margin: 5px}

سيتم إدراج القاعدة الأولى في خريطة الفئة. والثاني في خريطة المعرّفات والثالث في خريطة العلامات.

بالنسبة إلى مقتطف HTML التالي:

<p class="error">an error occurred</p>
<div id=" messageDiv">this is a message</div>

سنحاول أولاً العثور على قواعد للعنصر p. ستحتوي خريطة الفئة على مفتاح "error" (خطأ) يتم العثور ضمنه على القاعدة "p.error". سيحتوي عنصر div على قواعد ذات صلة في خريطة المعرّف (المفتاح هو المعرّف) وخريطة العلامات. وبالتالي، يبقى فقط العثور على القواعد التي تم استخراجها من خلال المفاتيح والتي تتطابق فعلاً.

على سبيل المثال، إذا كانت قاعدة div هي:

table div {margin: 5px}

سيظل يتم استخراجه من خريطة العلامات، لأن المفتاح هو المحدد الموجود في أقصى اليمين، لكنه لن يتطابق مع عنصر div الخاص بنا، والذي لا يحتوي على عنصر جدول سابق.

ويتم إجراء هذا التعديل في كل من WebKit وFirefox.

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

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

تبدأ المشكلة عندما يكون هناك أكثر من تعريف واحد - وهنا يأتي ترتيب التتالي لحل المشكلة.

يمكن أن يظهر تعريف سمة النمط في عدة أوراق أنماط، وعدة مرات داخل ورقة أنماط. وهذا يعني أنّ ترتيب تطبيق القواعد مهم جدًا. ويُعرف ذلك باسم ترتيب "التسلسل". وفقًا لمواصفات CSS2، يكون ترتيب التتالي (من الأدنى إلى الأعلى) كما يلي:

  1. تصريحات المتصفّح
  2. البيانات العادية للمستخدم
  3. التصريحات العادية للمؤلف
  4. كتابة بيانات إفصاح مهمة
  5. تصريحات مهمة للمستخدم

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

الدقة

يتم تحديد خصوصية أداة الاختيار من خلال مواصفات CSS2 على النحو التالي:

  1. يتم احتساب 1 إذا كان البيان الذي ينتمي إليه هو سمة "style" وليس قاعدة تتضمّن أداة اختيار، و0 في الحالات الأخرى (= a)
  2. احتساب عدد سمات أرقام التعريف في أداة الاختيار (= b)
  3. احسب عدد السمات الأخرى والفئات الزائفة في أداة الاختيار (= c)
  4. احتسِب عدد أسماء العناصر والعناصر الزائفة في أداة الاختيار (= d)

يؤدي دمج الأرقام الأربعة a-b-c-d (في نظام أرقام ذي أساس كبير) إلى تحديد الخصوصية.

يتم تحديد قاعدة الأرقام التي تحتاج إلى استخدامها من خلال أعلى عدد لديك في إحدى الفئات.

على سبيل المثال، إذا كانت قيمة a تساوي 14، يمكنك استخدام نظام العد الستة عشري. في الحالة غير المحتملة حيث a=17، ستحتاج إلى قاعدة أرقام مكونة من 17 رقمًا. يمكن أن تحدث الحالة الأخيرة مع أداة اختيار مثل: html body div div p… (17 علامة في أداة الاختيار… ليس من المرجح جدًا).

إليك بعض الأمثلة:

 *             {}  /* a=0 b=0 c=0 d=0 -> specificity = 0,0,0,0 */
 li            {}  /* a=0 b=0 c=0 d=1 -> specificity = 0,0,0,1 */
 li:first-line {}  /* a=0 b=0 c=0 d=2 -> specificity = 0,0,0,2 */
 ul li         {}  /* a=0 b=0 c=0 d=2 -> specificity = 0,0,0,2 */
 ul ol+li      {}  /* a=0 b=0 c=0 d=3 -> specificity = 0,0,0,3 */
 h1 + *[rel=up]{}  /* a=0 b=0 c=1 d=1 -> specificity = 0,0,1,1 */
 ul ol li.red  {}  /* a=0 b=0 c=1 d=3 -> specificity = 0,0,1,3 */
 li.red.level  {}  /* a=0 b=0 c=2 d=1 -> specificity = 0,0,2,1 */
 #x34y         {}  /* a=0 b=1 c=0 d=0 -> specificity = 0,1,0,0 */
 style=""          /* a=1 b=0 c=0 d=0 -> specificity = 1,0,0,0 */

ترتيب القواعد

بعد مطابقة القواعد، يتم ترتيبها وفقًا لقواعد التتالي. يستخدم WebKit الترتيب الفقاعي للقوائم الصغيرة والترتيب بالدمج للقوائم الكبيرة. ينفّذ WebKit عملية الترتيب من خلال إلغاء عامل التشغيل > للقواعد:

static bool operator >(CSSRuleData& r1, CSSRuleData& r2)
{
    int spec1 = r1.selector()->specificity();
    int spec2 = r2.selector()->specificity();
    return (spec1 == spec2) ? r1.position() > r2.position() : spec1 > spec2;
}

عملية تدريجية

يستخدم WebKit علامة تشير إلى ما إذا تم تحميل جميع أوراق الأنماط ذات المستوى الأعلى (بما في ذلك @imports). إذا لم يتم تحميل النمط بالكامل عند الإرفاق، يتم استخدام العناصر النائبة ويتم وضع علامة عليها في المستند، وستتم إعادة احتسابها بعد تحميل أوراق الأنماط.

التنسيق

عند إنشاء المعالج وإضافته إلى الشجرة، لا يكون له موضع وحجم. يُطلق على عملية حساب هذه القيم اسم "التنسيق" أو "إعادة التدفق".

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

يكون نظام الإحداثيات مرتبطًا بالإطار الجذر. يتم استخدام الإحداثيات العلوية واليسرى.

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

موضع العارض الجذر هو 0,0 وأبعاده هي إطار العرض، أي الجزء المرئي من نافذة المتصفّح.

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

نظام البت المتغيّر

لتجنُّب إجراء تنسيق كامل لكل تغيير صغير، تستخدم المتصفّحات نظام "dirty bit". يضع العارض الذي تم تغييره أو إضافته علامة على نفسه وعلى عناصره الثانوية على أنّها "غير صالحة"، أي أنّها بحاجة إلى تنسيق.

هناك علامتان: "غير صالح" و "العناصر الفرعية غير صالحة"، ما يعني أنّه على الرغم من أنّ المعالج نفسه قد يكون صالحًا، إلا أنّه يحتوي على عنصر فرعي واحد على الأقل يحتاج إلى تنسيق.

التنسيق العام والتنسيق المتزايد

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

  1. تغيير في النمط العام يؤثر في جميع أدوات العرض، مثل تغيير حجم الخط
  2. نتيجةً لتغيير حجم الشاشة

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

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

التنسيق المتزايد
الشكل 18: التنسيق المتزايد - يتم وضع أدوات العرض غير الصالحة وعناصرها الفرعية فقط

التخطيط غير المتزامن والمتزامن

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

يمكن أن تؤدي النصوص البرمجية التي تطلب معلومات عن الأنماط، مثل "offsetHeight"، إلى تشغيل التنسيق المتزايد بشكل متزامن.

سيتم عادةً تشغيل التنسيق العام بشكل متزامن.

في بعض الأحيان، يتم تشغيل التنسيق كاستدعاء بعد التنسيق الأولي لأن بعض السمات، مثل موضع التمرير، قد تغيرت.

التحسينات

عندما يتم تشغيل التنسيق من خلال "تغيير الحجم" أو تغيير في موضع العارض(وليس الحجم)، يتم أخذ أحجام العرض من ذاكرة التخزين المؤقت ولا تتم إعادة حسابها…

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

عملية التخطيط

عادةً ما يكون للتنسيق النمط التالي:

  1. يحدّد عارض المحتوى الرئيسي عرضه الخاص.
  2. يراجع أحد الوالدَين المحتوى مع الأطفال ويشرح لهم ما يلي:
    1. Place the child renderer (sets its x and y).
    2. يستدعي تنسيق الطفل إذا لزم الأمر، أي إذا كان غير صالح أو إذا كنا في تنسيق عام أو لسبب آخر، ما يؤدي إلى احتساب ارتفاع الطفل.
  3. يستخدم العنصر الرئيسي الارتفاعات التراكمية للعناصر الثانوية وارتفاعات الهوامش والحشو لضبط ارتفاعه الخاص، وسيتم استخدام ذلك من قِبل العنصر الرئيسي لأداة العرض الرئيسية.
  4. يضبط البت غير الصالح على "خطأ".

يستخدم Firefox كائن "حالة" (nsHTMLReflowState) كمَعلمة للتنسيق (يُطلق عليها "إعادة التدفق"). ومن بين أمور أخرى، تتضمّن الحالة عرض العنصر الرئيسي.

ناتج تنسيق Firefox هو كائن "مقاييس" (nsHTMLReflowMetrics). سيحتوي على الارتفاع المحسوب للعرض.

احتساب العرض

يتم احتساب عرض المعالج باستخدام عرض كتلة الحاوية، وسمة "العرض" لنمط المعالج، والهوامش والحدود.

على سبيل المثال، عرض div التالي:

<div style="width: 30%"/>

سيتم احتسابها بواسطة WebKit على النحو التالي(فئة RenderBox طريقة calcWidth):

  • عرض الحاوية هو الحد الأقصى للعرض المتاح للحاويات و0. ‫availableWidth في هذه الحالة هو contentWidth الذي يتم احتسابه على النحو التالي:
clientWidth() - paddingLeft() - paddingRight()

يمثّل clientWidth وclientHeight الجزء الداخلي من العنصر باستثناء الحدود وشريط التمرير.

  • عرض العناصر هو سمة النمط "width". وسيتم احتسابها كقيمة مطلقة من خلال احتساب النسبة المئوية لعرض الحاوية.

  • تمت الآن إضافة الحدود الأفقية والحشوات.

حتى الآن، كان هذا هو حساب "العرض المفضّل". الآن سيتم حساب الحد الأدنى والحد الأقصى للعرض.

إذا كان العرض المفضّل أكبر من الحد الأقصى للعرض، يتم استخدام الحد الأقصى للعرض. إذا كان أقل من الحد الأدنى للعرض (أصغر وحدة غير قابلة للكسر)، فسيتم استخدام الحد الأدنى للعرض.

يتم تخزين القيم مؤقتًا في حال الحاجة إلى تنسيق، ولكن لا يتغيّر العرض.

تقسيم الأسطر

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

الرسم بالألوان

في مرحلة الرسم، يتم اجتياز شجرة العرض واستدعاء طريقة "paint()‎" الخاصة بالعارض لعرض المحتوى على الشاشة. يستخدم الرسم مكوّن البنية الأساسية لواجهة المستخدم.

عالمية وتدريجية

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

ترتيب الرسم

تحدّد CSS2 ترتيب عملية الرسم. هذا هو في الواقع الترتيب الذي يتم به تجميع العناصر في سياقات التجميع. يؤثر هذا الترتيب في الرسم لأنّ المجموعات يتم رسمها من الخلف إلى الأمام. ترتيب التجميع لعارض الكتل هو:

  1. لون الخلفية
  2. صورة الخلفية
  3. border
  4. الأطفال
  5. outline

قائمة عرض Firefox

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

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

يحسّن Firefox العملية من خلال عدم إضافة عناصر سيتم إخفاؤها، مثل العناصر التي تقع بالكامل تحت عناصر أخرى معتمة.

مساحة تخزين مستطيل WebKit

قبل إعادة الرسم، يحفظ WebKit المستطيل القديم كصورة نقطية. بعد ذلك، يتم رسم الفارق بين المستطيلات الجديدة والقديمة فقط.

التغييرات الديناميكية

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

سلاسل محادثات محرّك العرض

يستخدم محرّك العرض سلسلة تعليمات واحدة. يتم تنفيذ كل شيء تقريبًا، باستثناء عمليات الشبكة، في سلسلة محادثات واحدة. في Firefox وSafari، هذا هو سلسلة التعليمات الرئيسية للمتصفّح. وفي Chrome، هو سلسلة التعليمات الرئيسية لعملية علامة التبويب.

يمكن تنفيذ عمليات الشبكة من خلال عدة سلاسل تعليمات متوازية. عدد الاتصالات المتوازية محدود (عادةً ما بين اتصالَين و6 اتصالات).

حلقة معالجة الأحداث

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

while (!mExiting)
    NS_ProcessNextEvent(thread);

النموذج المرئي CSS2

لوحة الرسم

وفقًا لمواصفات CSS2، يصف المصطلح "لوحة الرسم" "المساحة التي يتم فيها عرض بنية التنسيق"، أي المكان الذي يعرض فيه المتصفّح المحتوى.

تكون لوحة الرسم غير محدودة لكل سمة من سمات المساحة، ولكن تختار المتصفّحات عرضًا أوليًا استنادًا إلى سمات إطار العرض.

وفقًا للموقع الإلكتروني www.w3.org/TR/CSS2/zindex.html، تكون اللوحة شفافة إذا كانت مضمّنة في لوحة أخرى، ويتم منحها لونًا يحدّده المتصفّح إذا لم تكن كذلك.

نموذج CSS box

يصف نموذج CSS box المربّعات المستطيلة التي يتم إنشاؤها للعناصر في شجرة المستندات ويتم تنسيقها وفقًا لنموذج التنسيق المرئي.

يحتوي كل مربّع على منطقة محتوى (مثل نص أو صورة أو غير ذلك) ومساحة متروكة اختيارية محيطة به وحدود ومساحات هامشية.

نموذج CSS2 box
الشكل 19: نموذج مربّع CSS2

تنشئ كل عقدة من 0 إلى n من هذه المربّعات.

تحتوي جميع العناصر على سمة "display" تحدّد نوع المربّع الذي سيتم إنشاؤه.

أمثلة:

block: generates a block box.
inline: generates one or more inline boxes.
none: no box is generated.

القيمة التلقائية هي inline، ولكن قد تضبط ورقة أنماط المتصفّح قيمًا تلقائية أخرى. على سبيل المثال: العرض التلقائي للعنصر "div" هو block.

يمكنك العثور على مثال لورقة أنماط تلقائية هنا: www.w3.org/TR/CSS2/sample.html.

مخطّط تحديد الموقع

هناك ثلاثة مخططات:

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

يتم ضبط مخطط تحديد الموضع من خلال السمة "position" والسمة "float".

  • static وrelative يتسببان في تدفق عادي
  • تؤدي القيمتان absolute وfixed إلى تحديد الموضع المطلق

في تحديد الموضع الثابت، لا يتم تحديد أي موضع ويتم استخدام الموضع التلقائي. في المخططات الأخرى، يحدّد المؤلف الموضع: في الأعلى أو الأسفل أو اليمين أو اليسار.

يتم تحديد طريقة تخطيط المربّع من خلال:

  • نوع الصندوق
  • أبعاد الصندوق
  • مخطّط تحديد الموقع
  • المعلومات الخارجية، مثل حجم الصورة وحجم الشاشة

أنواع الصناديق

مربّع الكتلة: يشكّل كتلة، وله مستطيل خاص به في نافذة المتصفّح.

مربّع الحظر
الشكل 20: مربّع الحظر

مربّع مضمّن: ليس له كتلة خاصة به، ولكنه يقع داخل كتلة حاوية.

المربّعات المضمّنة
الشكل 21: المربّعات المضمّنة

يتم تنسيق القوالب عموديًا واحدًا تلو الآخر. يتم تنسيق العناصر المضمّنة أفقيًا.

تنسيق الكتلة والتنسيق المضمّن
الشكل 22: تنسيق الفقرات والأسطر

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

الخطوط:
الشكل 23: الخطوط

إقناع العملاء بالتبديل

قريب

ضبط الموضع نسبيًا: يتم ضبط الموضع كالمعتاد ثم نقله حسب القيمة المطلوبة.

ضبط الموضع نسبيًا
الشكل 24: ضبط الموضع نسبيًا

عائمة

يتم نقل مربّع عائم إلى يمين السطر أو يساره. الميزة المثيرة للاهتمام هي أن المربعات الأخرى تتدفق حولها. رمز HTML:

<p>
  <img style="float: right" src="images/image.gif" width="100" height="100">
  Lorem ipsum dolor sit amet, consectetuer...
</p>

سيظهر على النحو التالي:

عدد عائم
الشكل 25: Float

مطلق وثابت

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

تحديد الموضع الثابت.
الشكل 26: موضع ثابت

تمثيل ذو طبقات

يتم تحديد ذلك من خلال سمة z-index في CSS. وهي تمثّل البُعد الثالث للمربّع، أي موضعه على طول "المحور z".

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

يتم ترتيب المجموعات وفقًا لخاصية z-index. تشكّل المربّعات التي تتضمّن السمة "z-index" مكدّسًا محليًا. يحتوي إطار العرض على الحزمة الخارجية.

مثال:

<style type="text/css">
  div {
    position: absolute;
    left: 2in;
    top: 2in;
  }
</style>

<p>
  <div
    style="z-index: 3;background-color:red; width: 1in; height: 1in; ">
  </div>
  <div
    style="z-index: 1;background-color:green;width: 2in; height: 2in;">
  </div>
</p>

ستكون النتيجة على النحو التالي:

تحديد الموضع الثابت.
الشكل 27: الموضع الثابت

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

الموارد

  1. بنية المتصفّح

    1. Grosskurth, Alan. بنية مرجعية لمتصفّحات الويب (ملف PDF)
    2. Gupta, Vineet. How Browsers Work - Part 1 - Architecture
  2. التحليل

    1. Aho, Sethi, Ullman, Compilers: Principles, Techniques, and Tools (aka the "Dragon book"), Addison-Wesley, 1986
    2. "ريك جيليف" The Bold and the Beautiful: two new drafts for HTML 5.
  3. Firefox

    1. L. ديفيد بارون، Faster HTML and CSS: Layout Engine Internals for مطوِّري تطبيقات الويب.
    2. L. ديفيد بارون، تسريع HTML وCSS: العناصر الداخلية لمحرك التنسيق لمطوّري تطبيقات الويب (فيديو Google tech talk)
    3. L. "ديفيد بارون"، محرّك التنسيق في Mozilla
    4. L. "ديفيد بارون"، مستندات نظام الأنماط في Mozilla
    5. &quot;كريس واترسون&quot;، ملاحظات حول إعادة تدفق HTML
    6. "كريس واترسون"، نظرة عامة على Gecko
    7. ألكسندر لارسون، دورة حياة طلب HTTP في HTML
  4. WebKit

    1. ديفيد هيات، تنفيذ CSS(الجزء 1)
    2. ديفيد هيات، نظرة عامة على WebCore
    3. "ديفيد هيات"، عرض WebCore
    4. ديفيد هيات، مشكلة FOUC
  5. مواصفات W3C

    1. مواصفات HTML 4.01
    2. مواصفات W3C HTML5
    3. مواصفات صفحات الأنماط المتتالية المستوى 2 المراجعة 1 (CSS 2.1)
  6. تعليمات إنشاء المتصفّحات

    1. ‫Firefox: https://developer.mozilla.org/Build_Documentation
    2. WebKit. http://webkit.org/building/build.html

الترجمات

تمت ترجمة هذه الصفحة إلى اليابانية مرتين:

يمكنك الاطّلاع على الترجمات المستضافة خارجيًا باللغتَين الكورية والتركية.

شكرًا للجميع!