پشت صحنه مرورگرهای وب مدرن
پیشگفتار
این کتاب راهنمای جامع درباره عملیات داخلی WebKit و Gecko نتیجه تحقیقات زیادی است که توسط توسعهدهنده اسرائیلی، تالی گارسیل، انجام شده است. طی چند سال، او تمام دادههای منتشرشده درباره اجزای داخلی مرورگر را بررسی کرد و زمان زیادی را صرف خواندن کد منبع مرورگر وب کرد. او نوشت:
بهعنوان توسعهدهنده وب، آشنایی با عملکرد داخلی مرورگر به شما کمک میکند تصمیمات بهتری بگیرید و توجیهات پشت بهترین شیوههای توسعه را درک کنید. اگرچه این سند نسبتاً طولانی است، توصیه میکنیم کمی وقت بگذارید و آن را بررسی کنید. از این تصمیم خرسند خواهید شد.
Paul Irish، روابط توسعهدهنده Chrome
مقدمه
مرورگرهای وب پرکاربردترین نرمافزارها هستند. در این کتاب مقدماتی، توضیح میدهم که
آنها در پشت صحنه چگونه کار میکنند. خواهیم دید وقتی google.com را در نوار نشانی تایپ میکنید چه اتفاقی میافتد تا زمانی که صفحه Google را در صفحه مرورگر ببینید.
مرورگرهایی که دربارهشان صحبت خواهیم کرد
امروزه پنج مرورگر اصلی در رایانه استفاده میشود: Chrome، Internet Explorer، Firefox، Safari، و Opera. در تلفن همراه، مرورگرهای اصلی عبارتند از مرورگر Android، iPhone، Opera Mini و Opera Mobile، UC Browser، مرورگرهای Nokia S40/S60 و Chrome، که همه آنها، به جز مرورگرهای Opera، مبتنی بر WebKit هستند. من از مرورگرهای متنباز Firefox و Chrome و Safari (که تا حدودی متنباز است) مثال میآورم. طبق آمار StatCounter (از ژوئن ۲۰۱۳) Chrome، Firefox و Safari حدود ۷۱٪ از استفاده جهانی مرورگر رایانهای را تشکیل میدهند. در تلفن همراه، مرورگر Android، iPhone، و Chrome حدود ۵۴٪ از استفاده را تشکیل میدهند.
عملکرد اصلی مرورگر
عملکرد اصلی مرورگر ارائه منبع وب انتخابی شما با درخواست آن از سرور و نمایش آن در پنجره مرورگر است. منبع معمولاً سند HTML است، اما ممکن است PDF، تصویر، یا نوع دیگری از محتوا نیز باشد. مکان منبع توسط کاربر بااستفاده از URI (شناسه منبع یکسان) مشخص میشود.
نحوه تفسیر و نمایش فایلهای HTML توسط مرورگر در مشخصات HTML و CSS مشخص شده است. این مشخصات توسط سازمان W3C (کنسرسیوم وب جهانگستر) که سازمان استانداردهای وب است نگهداری میشود. سالها مرورگرها فقط با بخشی از مشخصات مطابقت داشتند و افزونههای خودشان را توسعه میدادند. این امر باعث مشکلات جدی سازگاری برای نویسندگان وب شد. امروزه اکثر مرورگرها کموبیش با مشخصات مطابقت دارند.
میاناهای کاربر مرورگرها شباهتهای زیادی با یکدیگر دارند. ازجمله عناصر رایج میانای کاربر عبارتاند از:
- نوار نشانی برای درج URI
- دکمههای برگشت و جلو
- گزینههای نشانکگذاری
- دکمههای بازآوری و توقف برای بازآوری یا توقف بار کردن اسناد فعلی
- دکمه صفحه اصلی که شما را به صفحه اصلیتان میبرد
عجیب است که میانای کاربر مرورگر در هیچ مشخصات رسمیای مشخص نشده است، بلکه فقط از روشهای خوبی ناشی میشود که درطول سالها تجربه و با تقلید مرورگرها از یکدیگر شکل گرفتهاند. مشخصات HTML5 عناصر میانای کاربری را که مرورگر باید داشته باشد تعریف نمیکند، اما برخیاز عناصر رایج را فهرست میکند. نوار نشانی، نوار وضعیت، و نوار ابزار ازجمله این عناصر هستند. البته ویژگیهایی وجود دارد که مختص مرورگر خاصی است، مثل مدیر بارگیری Firefox.
زیرساخت سطح بالا
مؤلفههای اصلی مرورگر عبارتاند از:
- میانای کاربر: شامل نوار نشانی، دکمه عقب/جلو، منو نشانکگذاری، و غیره میشود. همه بخشهای نمایشگر مرورگر بهجز پنجرهای که صفحه درخواستشده را در آن میبینید.
- موتور مرورگر: کنشها را بین واسط کاربر و موتور پردازنده مرتب میکند.
- موتور پردازش: مسئول نمایش محتوای درخواستشده است. برای مثال، اگر محتوای درخواستشده HTML باشد، موتور پردازنده HTML و CSS را تجزیه میکند و محتوای تجزیهشده را روی صفحهنمایش نشان میدهد.
- شبکهسازی: برای تماسهای شبکه مانند درخواستهای HTTP، بااستفاده از پیادهسازیهای مختلف برای پلاتفرمهای مختلف در پشت یک میانای مستقل از پلاتفرم.
- بخش پشتی میانای کاربر: برای ترسیم ابزارههای اصلی مانند چارگوشهای ترکیبی و پنجرهها استفاده میشود. این پسوند یک رابط عمومی را نشان میدهد که مختص پلاتفرم نیست. در پسزمینه، از روشهای میانای کاربر سیستمعامل استفاده میکند.
- مفسر جاوا اسکریپت. برای تجزیه و اجرای کد جاوا اسکریپت استفاده میشود.
- ذخیرهسازی داده. این لایه پایداری است. مرورگر ممکن است نیاز داشته باشد انواع دادهها را بهصورت محلی ذخیره کند، مثل کوکیها. مرورگرها همچنین از سازوکارهای ذخیرهسازی مانند localStorage، IndexedDB، WebSQL، و FileSystem پشتیبانی میکنند.
توجه به این نکته مهم است که مرورگرهایی مانند Chrome چندین نمونه از موتور پردازش را اجرا میکنند: یکی برای هر برگه. هر برگه در فرایندی جداگانه اجرا میشود.
موتورهای پرداز زدن
مسئولیت موتور پرداز… پرداز است، یعنی نمایش محتوای درخواستشده در صفحه مرورگر.
بهطور پیشفرض، موتور پردازش میتواند تصاویر و اسناد HTML و XML را نمایش دهد. این برنامه میتواند انواع دیگر دادهها را ازطریق افزونهها یا برافزاها نمایش دهد؛ برای مثال، نمایش اسناد PDF بااستفاده از افزونه نمایشگر PDF. بااینحال، در این فصل روی مورد استفاده اصلی تمرکز خواهیم کرد: نمایش HTML و تصاویری که بااستفاده از CSS قالببندی شدهاند.
مرورگرهای مختلف از موتورهای پردازش مختلفی استفاده میکنند: Internet Explorer از Trident، Firefox از Gecko، و Safari از WebKit استفاده میکند. Chrome و Opera (از نسخه ۱۵) از Blink، انشعابی از WebKit، استفاده میکنند.
WebKit یک موتور پرداز متنباز است که بهعنوان موتوری برای پلاتفرم Linux شروع به کار کرد و توسط Apple برای پشتیبانی از Mac و Windows اصلاح شد.
جریان اصلی
موتور پرداز شروع به دریافت محتوای سند درخواستشده از لایه شبکه میکند. این کار معمولاً در بخشهای ۸ کیلوبایتی انجام میشود.
پساز آن، جریان اصلی موتور پردازش به این صورت است:
موتور پردازش شروع به تجزیه سند HTML میکند و عناصر را به گرههای DOM در درختی بهنام «درخت محتوا» تبدیل میکند. موتور دادههای سبک را هم در فایلهای CSS خارجی و هم در عناصر سبک تجزیه میکند. اطلاعات سبکبندی بههمراه دستورالعملهای بصری در HTML برای ایجاد درخت دیگری استفاده خواهد شد: درخت رندر.
درخت رندر شامل مستطیلهایی با ویژگیهای بصری مانند رنگ و ابعاد است. مستطیلها بهترتیب درست برای نمایش در صفحه قرار دارند.
پساز ساخت درخت رندر، فرایند «چیدمان» انجام میشود. این یعنی به هر گره مختصات دقیقی داده میشود که باید در آنجا روی صفحه نمایش داده شود. مرحله بعدی رنگآمیزی است - درخت پردازش پیمایش میشود و هر گره بااستفاده از لایه پسزمینه واسط کاربر رنگآمیزی میشود.
مهم است بدانید که این فرایندی تدریجی است. برای تجربه کاربری بهتر، موتور پردازش سعی میکند محتوا را در اسرع وقت روی صفحهنمایش نشان دهد. پیشاز شروع ساختن و چیدمان درخت رندر، منتظر نمیماند تا همه HTML تجزیه شود. بخشهایی از محتوا تجزیه و نمایش داده میشود، درحالیکه این فرایند با بقیه محتوا که همچنان از شبکه دریافت میشود ادامه مییابد.
نمونههای جریان اصلی
از شکلهای ۳ و ۴ میتوانید ببینید که اگرچه WebKit و Gecko از اصطلاحات کمی متفاوت استفاده میکنند، اما جریان اساساً یکسان است.
Gecko درخت عناصر قالببندیشده بصری را «درخت قاب» مینامد. هر عنصر یک قاب است. WebKit از اصطلاح «درخت پردازش» استفاده میکند و این درخت از «اشیاء پردازش» تشکیل شده است. WebKit از اصطلاح «چیدمان» برای قرار دادن عناصر استفاده میکند، درحالیکه Gecko آن را «Reflow» مینامد. «پیوست» اصطلاح WebKit برای اتصال گرههای DOM و اطلاعات بصری برای ایجاد درخت رندر است. یک تفاوت جزئی غیرمعنایی این است که Gecko یک لایه اضافی بین HTML و درخت DOM دارد. به آن «سینک محتوا» میگویند و کارخانهای برای ساخت عناصر DOM است. درباره هر بخش از جریان صحبت خواهیم کرد:
تجزیه - عمومی
ازآنجاییکه تجزیه فرایند بسیار مهمی در موتور پردازشی است، کمی عمیقتر به آن خواهیم پرداخت. بیایید با مقدمه کوتاهی درباره تجزیه کردن شروع کنیم.
تجزیه سند یعنی ترجمه آن به ساختاری که کد بتواند از آن استفاده کند. نتیجه تجزیه معمولاً درختی از گرهها است که ساختار سند را نشان میدهد. به این درخت تجزیه یا درخت نحو میگویند.
برای مثال، تجزیه عبارت 2 + 3 - 1 میتواند این درخت را برگرداند:
دستور زبان
تجزیه براساس قوانین نحوی است که سند از آن پیروی میکند: زبان یا قالبی که سند به آن نوشته شده است. هر قالبی که میتوانید تجزیه کنید باید دارای دستور زبان قطعی متشکل از واژگان و قواعد نحوی باشد. به آن دستور زبان مستقل از متن میگویند. زبانهای انسانی چنین زبانهایی نیستند و بنابراین نمیتوان آنها را با تکنیکهای تجزیه مرسوم تجزیه کرد.
ترکیب تجزیهکننده - تحلیلگر واژگانی
تجزیه را میتوان به دو زیرفرایند تقسیم کرد: تحلیل واژگانی و تحلیل نحوی.
تحلیل واژگانی فرایند تقسیم ورودی به نشانها است. نشانها واژگان زبان هستند: مجموعهای از واحدهای سازنده معتبر. در زبان انسان، این مجموعه شامل همه کلماتی است که در فرهنگ لغت آن زبان وجود دارد.
تحلیل نحوی یعنی اعمال قوانین نحوی زبان.
تجزیهکنندهها معمولاً کار را بین دو مؤلفه تقسیم میکنند: تحلیلگر واژگانی (گاهیاوقات توکنساز نامیده میشود) که مسئول تقسیم ورودی به توکنهای معتبر است، و تجزیهکننده که مسئول ساختن درخت تجزیه با تحلیل ساختار سند طبق قوانین نحو زبان است.
تحلیلگر واژگانی میداند چگونه نویسههای نامربوط مانند فاصلههای سفید و شکستگیهای خط را حذف کند.
فرایند تجزیه تکراری است. تجزیهکننده معمولاً از تحلیلگر واژگانی درخواست یک نشانه جدید میکند و سعی میکند نشانه را با یکی از قوانین نحو مطابقت دهد. اگر قاعدهای مطابقت داشته باشد، گرهای مربوط به آن کد به درخت تجزیه اضافه میشود و تجزیهکننده کد دیگری را درخواست میکند.
اگر هیچ قانونی مطابقت نداشته باشد، تجزیهکننده کد را بهطور داخلی ذخیره میکند و به درخواست کد ادامه میدهد تا زمانی که قانونی مطابق با همه کدهای ذخیرهشده داخلی پیدا شود. اگر قانونی پیدا نشود، تجزیهکننده استثنایی را مطرح خواهد کرد. این یعنی سند معتبر نبوده و خطاهای نحوی داشته است.
ترجمه
در بسیاریاز موارد، درخت تجزیه محصول نهایی نیست. تجزیه کردن اغلب در ترجمه استفاده میشود: تبدیل سند ورودی به قالبی دیگر. یک نمونه از آن گردآوری است. همگردانی که کد منبع را به کد ماشینی همگردانی میکند، ابتدا آن را به درخت تجزیه تجزیه میکند و سپس درخت را به سند کد ماشینی ترجمه میکند.
نمونه تجزیه کردن
در شکل ۵، درخت تجزیهای از عبارت ریاضی ساختیم. بیایید یک زبان ریاضی ساده را تعریف کنیم و فرایند تجزیه را ببینیم.
نحو:
- واحدهای سازنده نحو زبان عبارتاند از عبارتها، اصطلاحات، و عملیات.
- زبان ما میتواند شامل هر تعداد عبارت باشد.
- عبارت بهصورت یک «جمله» تعریف میشود که بهدنبال آن یک «عملگر» و سپس جمله دیگری میآید
- عملیات یک نشانه بهعلاوه یا یک نشانه بهمنها است
- جمله عبارت است از یک عبارت یا یک نشانه عدد صحیح
بیایید ورودی 2 + 3 - 1 را تحلیل کنیم.
اولین رشته فرعی که با قانون مطابقت دارد 2 است: طبق قانون شماره ۵، این یک عبارت است.
مطابقت دوم 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 را بهعنوان عبارت شناسایی میکند (فرایند شناسایی عبارت تکامل مییابد و با قوانین دیگر مطابقت پیدا میکند، اما نقطه شروع بالاترین سطح قانون است).
تجزیهکننده پایین به بالا ورودی را تا زمانی که با قاعدهای مطابقت پیدا کند اسکن میکند. سپس ورودی منطبق را با قانون جایگزین میکند. این کار تا پایان ورودی ادامه خواهد داشت. عبارت نیمهمنطبق در پشته تجزیهکننده قرار میگیرد.
این نوع تجزیهکننده پایین به بالا، تجزیهکننده انتقال-کاهش نامیده میشود، زیرا ورودی به سمت راست منتقل میشود (اشارهگری را تصور کنید که ابتدا به شروع ورودی اشاره میکند و به سمت راست حرکت میکند) و بهتدریج به قوانین نحو کاهش مییابد.
تولید خودکار تجزیهکنندهها
ابزارهایی وجود دارد که میتوانند تجزیهکننده تولید کنند. شما دستور زبان خود را به آنها میدهید - واژگان و قوانین نحوی آن - و آنها یک تجزیهکننده کارآمد تولید میکنند. ایجاد تجزیهکننده نیازمند درک عمیقی از تجزیه است و ایجاد تجزیهکننده بهینهشده بهصورت دستی آسان نیست، بنابراین تولیدکنندههای تجزیهکننده میتوانند بسیار مفید باشند.
WebKit از دو تولیدکننده تجزیهکننده شناختهشده استفاده میکند: Flex برای ایجاد تحلیلگر واژگانی و Bison برای ایجاد تجزیهکننده (ممکن است با نامهای Lex و Yacc با آنها مواجه شوید). ورودی Flex فایلی است که حاوی تعاریف عبارت باقاعده از نشانهها است. ورودی Bison قوانین نحو زبان در قالب BNF است.
تجزیهکننده HTML
وظیفه تجزیهکننده HTML تجزیه نشانهگذاری HTML به درخت تجزیه است.
دستور زبان HTML
واژگان و نحو HTML در مشخصاتی که توسط سازمان W3C ایجاد شده است تعریف شدهاند.
همانطور که در مقدمه تجزیه دیدیم، نحو دستور زبان را میتوان بهطور رسمی بااستفاده از قالبهایی مانند BNF تعریف کرد.
متأسفانه هیچکدام از موضوعات تجزیهکننده مرسوم برای HTML کاربرد ندارند (من آنها را فقط برای سرگرمی مطرح نکردم - از آنها در تجزیه CSS و جاوا اسکریپت استفاده خواهد شد). HTML را نمیتوان بهراحتی با دستور زبان مستقل از بافت که تجزیهکنندهها نیاز دارند تعریف کرد.
قالب رسمیای برای تعریف HTML وجود دارد - DTD (تعریف نوع سند) - اما این قالب دستور زبان مستقل از بافت نیست.
این در نگاه اول عجیب بهنظر میرسد؛ HTML نسبتاً به XML نزدیک است. تجزیهکنندههای XML زیادی دردسترس است. یک نوع XML از HTML وجود دارد - XHTML - پس تفاوت بزرگ چیست؟
تفاوت این است که رویکرد HTML «بخشندهتر» است: به شما امکان میدهد برخیاز برچسبها را حذف کنید (که سپس بهطور ضمنی اضافه میشوند)، یا گاهی برچسبهای شروع یا پایان را حذف کنید، و غیره. بهطور کلی، این نحو «نرم» است، برخلاف نحو XML که سخت و دشوار است.
این جزئیات بهظاهر کوچک تفاوت بسیار زیادی ایجاد میکند. از یک طرف، این دلیل اصلی محبوبیت HTML است: اشتباهات شما را میبخشد و زندگی را برای نویسنده وب آسان میکند. ازسوی دیگر، نوشتن دستور زبان رسمی را دشوار میکند. بنابراین بهطور خلاصه، HTML را نمیتوان بهراحتی با تجزیهکنندههای مرسوم تجزیه کرد، زیرا دستور زبان آن مستقل از بافت نیست. HTML نمیتواند توسط تجزیهکنندههای XML تجزیه شود.
HTML DTD
تعریف HTML در قالب DTD است. این قالب برای تعریف زبانهای خانواده SGML استفاده میشود. این قالب حاوی تعاریف همه عناصر مجاز، مشخصهها، و سلسلهمراتب آنها است. همانطور که قبلاً دیدیم، HTML DTD دستور زبان مستقل از متن را تشکیل نمیدهد.
چند نوع مختلف از DTD وجود دارد. حالت سختگیرانه فقط با مشخصات مطابقت دارد، اما حالتهای دیگر از نشانهگذاریهایی که در گذشته توسط مرورگرها استفاده میشد پشتیبانی میکنند. هدف، سازگاری با نسخههای قدیمیتر محتوا است. DTD دقیق فعلی در اینجا است: www.w3.org/TR/html4/strict.dtd
DOM
درخت خروجی («درخت تجزیه») درختی از عنصر DOM و گرههای مشخصه است. DOM مخفف Document Object Model (مدل شیء سند) است. این شیء نمایش سند HTML و واسط عناصر HTML به دنیای خارج مانند جاوا اسکریپت است.
ریشه درخت شیء «سند» است.
DOM تقریباً رابطه یکبهیک با نشانهگذاری دارد. برای مثال:
<html>
<body>
<p>
Hello World
</p>
<div> <img src="example.png"/></div>
</body>
</html>
این نشانهگذاری به درخت DOM زیر ترجمه میشود:
DOM مانند HTML توسط سازمان 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 را نمیتوان بااستفاده از تجزیهکنندههای معمولی بالا به پایین یا پایین به بالا تجزیه کرد.
دلایل عبارتاند از:
- ماهیت بخشنده زبان.
- این واقعیت که مرورگرها تحمل خطای سنتی برای پشتیبانی از موارد شناختهشده HTML نامعتبر دارند.
- فرایند تجزیه قابلبازگشت است. برای زبانهای دیگر، منبع درطول تجزیه تغییر نمیکند، اما در HTML، کد پویا (مانند عناصر اسکریپت حاوی فراخوانیهای
document.write()) میتواند نشانهای اضافی اضافه کند، بنابراین فرایند تجزیه درواقع ورودی را اصلاح میکند.
مرورگرها که نمیتوانند از فنون تجزیه معمولی استفاده کنند، تجزیهکنندههای سفارشی برای تجزیه HTML ایجاد میکنند.
الگوریتم تجزیه بهطور دقیق در مشخصات HTML5 شرح داده شده است. این الگوریتم شامل دو مرحله است: نشانگذاری و ساختار درختی.
تبدیل به دادهواحد، تحلیل واژگانی است که ورودی را به دادهواحد تجزیه میکند. ازجمله نشانهای HTML میتوان به برچسبهای شروع، برچسبهای پایان، نامهای مشخصه، و مقادیر مشخصه اشاره کرد.
نشانهساز نشانه را تشخیص میدهد، آن را به سازنده درخت میدهد، و نویسه بعدی را برای تشخیص نشانه بعدی مصرف میکند، و این روند تا پایان ورودی ادامه مییابد.
الگوریتم تبدیل به دادهواحد
خروجی الگوریتم یک کد HTML است. الگوریتم بهصورت ماشین حالت بیان میشود. هر حالت یک یا چند نویسه از جریان ورودی را مصرف میکند و حالت بعدی را براساس آن نویسهها بهروز میکند. این تصمیم تحت تأثیر وضعیت فعلی نشانگذاری و وضعیت ساختار درختی است. این یعنی همان نویسه مصرفشده بسته به وضعیت فعلی، نتایج متفاوتی برای وضعیت صحیح بعدی بههمراه خواهد داشت. این الگوریتم پیچیدهتر از آن است که بتوان آن را بهطور کامل توصیف کرد، بنابراین بیایید یک مثال ساده را ببینیم که به ما در درک این اصل کمک میکند.
مثال ساده - نشانگذاری HTML زیر:
<html>
<body>
Hello world
</body>
</html>
وضعیت اولیه «وضعیت داده» است.
وقتی نویسه < پیدا میشود، وضعیت به «وضعیت باز برچسب» تغییر میکند.
مصرف نویسه a-z باعث ایجاد «نشان برچسب شروع» میشود، وضعیت به «وضعیت نام برچسب» تغییر میکند.
تا زمانی که نویسه > مصرف شود در این حالت میمانیم. هر نویسه به نام جدید کد اضافه میشود. در نمونه ما، کد ایجادشده یک کد html است.
وقتی به برچسب > میرسد، کد فعلی صادر میشود و وضعیت به «وضعیت داده» برمیگردد.
برچسب <body> با همان مراحل پردازش خواهد شد.
تاکنون برچسبهای html و body منتشر شدهاند. اکنون به «وضعیت داده» برگشتهایم.
مصرف نویسه H از Hello world باعث ایجاد و انتشار یک نشانه نویسه میشود، این روند تا رسیدن به < از </body> ادامه مییابد. ما برای هر نویسه از Hello world یک توکن نویسه صادر خواهیم کرد.
اکنون به «وضعیت باز برچسب» برگشتهایم.
Consuming the next input / will cause creation of an end tag token and a move to the "Tag name state". Again we stay in this state until we reach >.Then the new tag token will be emitted and we go back to the "Data state".
با ورودی </html> مانند مورد قبلی رفتار خواهد شد.
الگوریتم ساختار درختی
وقتی تجزیهکننده ایجاد میشود، شیء «سند» ایجاد میشود. درطول مرحله ساخت درخت، درخت DOM با «سند» در ریشه آن اصلاح خواهد شد و عناصری به آن اضافه خواهد شد. هر گرهای که توسط توکنیزهکننده منتشر میشود، توسط سازنده درخت پردازش خواهد شد. برای هر توکن، مشخصات تعیین میکند که کدام عنصر DOM با آن مرتبط است و برای این توکن ایجاد خواهد شد. عنصر به درخت DOM و همچنین پشته عناصر باز اضافه میشود. این پشته برای اصلاح عدم تطابقهای تودرتو و برچسبهای بستهنشده استفاده میشود. این الگوریتم بهعنوان ماشین حالت نیز توصیف میشود. این حالتها «حالتهای درج» نامیده میشوند.
بیایید فرایند ساخت درخت را برای ورودی نمونه ببینیم:
<html>
<body>
Hello world
</body>
</html>
ورودی مرحله ساخت درخت، توالیای از دادهواحدهای مرحله دادهواحدسازی است. حالت اول «حالت اولیه» است. دریافت کد «html» باعث انتقال به حالت «قبلاز html» و پردازش مجدد کد در آن حالت میشود. این کار باعث ایجاد عنصر HTMLHtmlElement میشود که به شیء ریشه Document اضافه خواهد شد.
وضعیت به «قبلاز سر» تغییر خواهد کرد. سپس کد «بدنه» دریافت میشود. یک HTMLHeadElement بهطور ضمنی ایجاد خواهد شد، اگرچه ما یک نشانه «head» نداریم و به درخت اضافه خواهد شد.
اکنون به حالت «در سر» و سپس به «بعداز سر» میرویم. نشان بدنه دوباره پردازش میشود، HTMLBodyElement ایجاد و درج میشود، و حالت به «در بدنه» منتقل میشود.
اکنون توکنهای نویسه رشته «Hello world» دریافت شده است. نویسه اول باعث ایجاد و درج گره «نوشتار» میشود و نویسههای دیگر به آن گره اضافه میشوند.
دریافت کردن دادهواحد پایان بدنه باعث انتقال به حالت «پساز بدنه» میشود. اکنون برچسب پایانی 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 زیادی را که بهدرستی شکل نگرفتهاند مدیریت کنیم، بنابراین تجزیهکننده باید نسبت به خطاها تحمل داشته باشد.
باید حداقل به شرایط خطای زیر رسیدگی کنیم:
- عنصری که اضافه میشود صریحاً در برخیاز برچسبهای بیرونی ممنوع شده است. در این حالت باید همه برچسبها را تا برچسبی که عنصر را ممنوع میکند ببندیم و سپس آن را اضافه کنیم.
- اجازه نداریم عنصر را مستقیماً اضافه کنیم. ممکن است شخصی که سند را نوشته است برخیاز برچسبهای میانی را فراموش کرده باشد (یا برچسب میانی اختیاری باشد). این مورد میتواند با برچسبهای زیر اتفاق بیفتد: HTML HEAD BODY TBODY TR TD LI (آیا موردی را فراموش کردم؟).
- میخواهیم عنصر بلوک را در داخل عنصر درونخطی اضافه کنیم. همه عناصر درونخطی را تا عنصر بلوک بالاتر بعدی ببندید.
- اگر این کار کمکی نکرد، عناصر را ببندید تا زمانی که اجازه افزودن عنصر را داشته باشیم - یا برچسب را نادیده بگیرید.
بیایید چند نمونه از تحمل خطای 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» مخفف شناسه است، مثل نام کلاس. «نام» شناسه عنصر است (که با «#» به آن ارجاع داده میشود)
دستور زبان نحو در 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 بهمعنای فاصله است). مجموعه قوانین شامل آکولاد است و داخل آن یک اعلان یا بهصورت اختیاری تعدادی اعلان وجود دارد که با نقطهویرگول از هم جدا شدهاند. «اعلامیه» و «انتخابگر» در تعاریف BNF زیر تعریف خواهند شد.
تجزیهکننده WebKit CSS
WebKit از تولیدکنندههای تجزیهکننده Flex و Bison برای ایجاد خودکار تجزیهکنندهها از فایلهای دستور زبان CSS استفاده میکند. همانطور که از مقدمه تجزیهکننده بهیاد دارید، Bison یک تجزیهکننده انتقال-کاهش پایین به بالا ایجاد میکند. Firefox از تجزیهکننده بالا به پایین استفاده میکند که بهصورت دستی نوشته شده است. در هر دو مورد، هر فایل CSS به یک شیء StyleSheet تجزیه میشود. هر شیء حاوی قوانین CSS است. اشیاء قانون CSS حاوی اشیاء گزینشگر و اعلامیه و اشیاء دیگر مطابق با دستور زبان CSS هستند.
ترتیب پردازش برای برگههای سبک و دستورات
متنها
مدل وب همگام است. نویسندگان انتظار دارند که وقتی تجزیهکننده به برچسب <script> میرسد، اسکریپتها فوراً تجزیه و اجرا شوند.
تجزیه سند تا زمانی که اسکریپت اجرا شود متوقف میشود.
اگر دستورگان خارجی باشد، ابتدا باید منبع از شبکه واکشی شود - این کار نیز بهصورت همزمان انجام میشود و تجزیه تا زمانی که منبع واکشی شود متوقف میشود.
این مدل برای سالها استفاده میشد و در مشخصات HTML4 و HTML5 نیز مشخص شده است.
نویسندگان میتوانند مشخصه «defer» را به یک اسکریپت اضافه کنند، در این صورت تجزیه سند متوقف نخواهد شد و پساز تجزیه سند اجرا خواهد شد. HTML5 گزینهای برای علامتگذاری متن بهعنوان ناهمگام اضافه میکند تا توسط رشته دیگری تجزیه و اجرا شود.
تجزیه حدسی
هم WebKit و هم Firefox این بهینهسازی را انجام میدهند. هنگام اجرای دستورگانها، رشته دیگری بقیه سند را تجزیه میکند و متوجه میشود که چه منابع دیگری باید از شبکه بارگیری شوند و آنها را بارگیری میکند. به این ترتیب، منابع را میتوان در اتصالهای موازی بار کرد و سرعت کلی بهبود مییابد. توجه: تجزیهکننده حدسی فقط ارجاعها به منابع خارجی مانند اسکریپتهای خارجی، برگههای سبک، و تصاویر را تجزیه میکند: درخت DOM را تغییر نمیدهد - این کار به تجزیهکننده اصلی واگذار میشود.
برگههای قالببندی
ازسوی دیگر، برگههای سبک مدل متفاوتی دارند. ازنظر مفهومی، بهنظر میرسد که چون برگههای سبک نگارش درخت مدل شیءگرای سند را تغییر نمیدهند، دلیلی برای منتظر ماندن برای آنها و توقف تجزیه سند وجود ندارد. بااینحال، مشکلی وجود دارد که دستورگانها در مرحله تجزیه سند، اطلاعات سبک را درخواست میکنند. اگر سبک هنوز بارگیری و تجزیه نشده باشد، اسکریپت پاسخهای اشتباه دریافت میکند و ظاهراً این امر مشکلات زیادی ایجاد کرده است. بهنظر میرسد این مورد خاص باشد اما بسیار رایج است. Firefox همه اسکریپتها را وقتی که یک برگه سبک هنوز درحال بارگیری و تجزیه است مسدود میکند. WebKit دستورگانها را فقط زمانی مسدود میکند که سعی کنند به ویژگیهای سبک خاصی دسترسی پیدا کنند که ممکن است تحت تأثیر شیوهنامههای بارنشده قرار بگیرند.
ساخت درخت پرداز زدن
در حین ساخت درخت DOM، مرورگر درخت دیگری به نام درخت رندر میسازد. این درخت از عناصر دیداری بهترتیب نمایش آنها تشکیل شده است. این تصویر، نمایش دیداری سند است. هدف این درخت این است که امکان رنگآمیزی محتوا به ترتیب صحیح فراهم شود.
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 شرح داده شده است. این شامل اطلاعات هندسی مانند عرض، ارتفاع و موقعیت است.
نوع چارگوش تحتتأثیر مقدار «نمایش» مشخصه سبک مربوط به گره قرار میگیرد (به بخش محاسبه سبک مراجعه کنید). در اینجا کد 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 مطابقت دارد، اما این رابطه یکبهیک نیست. عناصر غیردیداری DOM در درخت رندر درج نخواهند شد. یک نمونه عنصر «head» است. همچنین عناصری که مقدار نمایش آنها به «none» اختصاص داده شده است در درخت ظاهر نمیشوند (درحالیکه عناصر با نمایان بودن «hidden» در درخت ظاهر میشوند).
عناصر DOM وجود دارد که با چندین شیء دیداری مطابقت دارد. اینها معمولاً عناصری با ساختار پیچیده هستند که نمیتوان آنها را با یک مستطیل توصیف کرد. برای مثال، عنصر «انتخاب» سه پردازنده دارد: یکی برای ناحیه نمایش، یکی برای چارگوش فهرست کرکرهای، و یکی برای دکمه. همچنین وقتی نوشتار به چند خط تقسیم میشود چون عرض برای یک خط کافی نیست، خطوط جدید بهعنوان پردازنده اضافه میشوند.
نمونه دیگری از چند پردازنده HTML خراب است. طبق مشخصات CSS، عنصر درونخطی باید فقط حاوی عناصر بلوک یا فقط عناصر درونخطی باشد. درصورت وجود محتوای مختلط، رندرکنندههای بلوک ناشناس برای پوشاندن عناصر درونخطی ایجاد خواهند شد.
برخیاز اشیای پردازش با گره DOM مطابقت دارند اما نه در همان جای درخت. عناصر شناور و با موقعیت مطلق خارج از جریان هستند، در بخش دیگری از درخت قرار میگیرند، و به قاب واقعی نگاشت میشوند. قاب جایبان جایی است که باید قرار میگرفتند.
جریان ساختن درخت
در Firefox، ارائه بهعنوان شنونده برای بهروزرسانیهای DOM ثبت میشود.
ارائه، ایجاد قاب را به FrameConstructor واگذار میکند و سازنده سبک را حل میکند (به محاسبه سبک مراجعه کنید) و قاب ایجاد میکند.
در WebKit، فرایند حل کردن سبک و ایجاد ارائهدهنده تصویر «پیوست» نامیده میشود. هر گره DOM یک روش «پیوست» دارد. پیوست همگام است، درج گره در درخت DOM روش «پیوست» گره جدید را فراخوانی میکند.
پردازش برچسبهای html و body منجر به ساخت ریشه درخت رندر میشود.
شیء رندر ریشه با آنچه مشخصات CSS بلوک حاوی مینامد مطابقت دارد: بالاترین بلوکی که شامل همه بلوکهای دیگر است. ابعاد آن همان ناحیه نمایش است: ابعاد ناحیه نمایش پنجره مرورگر.
Firefox آن را ViewPortFrame و WebKit آن را RenderView مینامد.
این شیء پرداختی است که سند به آن اشاره میکند.
بقیه درخت بهعنوان درج گرههای DOM ساخته میشود.
مشخصات CSS2 را در مدل پردازش ببینید.
محاسبه سبک
ساختن درخت پردازش نیازمند محاسبه ویژگیهای بصری هر شیء پردازش است. این کار با محاسبه ویژگیهای سبک هر عنصر انجام میشود.
این سبک شامل برگههای سبک با منشاهای مختلف، عناصر سبک درونخطی، و ویژگیهای دیداری در HTML (مانند ویژگی «bgcolor») است. مورد آخر به ویژگیهای سبک CSS منطبق ترجمه میشود.
منشأ شیوهنامهها شیوهنامههای پیشفرض مرورگر، شیوهنامههای ارائهشده توسط نویسنده صفحه، و شیوهنامههای کاربر است - اینها شیوهنامههایی هستند که توسط کاربر مرورگر ارائه میشوند (مرورگرها به شما امکان میدهند سبکهای دلخواهتان را تعریف کنید. برای مثال، در Firefox، این کار با قرار دادن یک برگه سبک در پوشه «نمایه Firefox» انجام میشود).
محاسبه سبک چند مشکل ایجاد میکند:
- دادههای سبک ساختار بسیار بزرگی است که ویژگیهای سبک متعددی را در خود جای میدهد، این امر میتواند باعث مشکلات حافظه شود.
پیدا کردن قوانین منطبق برای هر عنصر میتواند باعث مشکلات عملکردی شود اگر بهینهسازی نشده باشد. پیمایش کل فهرست قانون برای هر عنصر جهت یافتن موارد منطبق، کار سنگینی است. انتخابگرها میتوانند ساختار پیچیدهای داشته باشند که میتواند باعث شود فرایند مطابقت دادن در مسیری بهظاهر امیدوارکننده شروع شود که ثابت شده بیفایده است و باید مسیر دیگری امتحان شود.
برای مثال، این انتخابگر ترکیبی:
div div div div{ ... }یعنی قوانین برای
<div>که فرزند ۳ div است اعمال میشود. فرض کنید میخواهید بررسی کنید که آیا این قانون برای عنصر<div>خاصی اعمال میشود یا نه. مسیر خاصی را در درخت برای بررسی انتخاب میکنید. ممکن است لازم باشد درخت گره را به بالا پیمایش کنید تا متوجه شوید فقط دو div وجود دارد و قانون اعمال نمیشود. سپس باید مسیرهای دیگر را در درخت امتحان کنید.اعمال قوانین شامل قوانین آبشاری نسبتاً پیچیدهای است که سلسلهمراتب قوانین را تعریف میکند.
بیایید ببینیم مرورگرها چگونه با این مشکلات مواجه میشوند:
همرسانی دادههای سبک
گرههای WebKit به اشیاء سبک (RenderStyle) ارجاع میدهند. این اشیاء را میتوان در برخی شرایط توسط گرهها همرسانی کرد. گرهها همنیا یا خویشاوند هستند و:
- عناصر باید در یک وضعیت موشواره باشند (برای مثال، یکی نمیتواند در وضعیت :hover باشد درحالیکه دیگری نیست)
- هیچکدام از عناصر نباید شناسه داشته باشند
- نام برچسبها باید مطابقت داشته باشد
- مشخصههای کلاس باید مطابقت داشته باشد
- مجموعه مشخصههای نگاشتشده باید یکسان باشد
- وضعیتهای پیوند باید مطابقت داشته باشد
- حالتهای تمرکز باید مطابقت داشته باشند
- هیچکدام از عناصر نباید تحتتأثیر انتخابگرهای مشخصه قرار بگیرند، که در آن تحتتأثیر قرار گرفتن بهصورت داشتن هرگونه مطابقت انتخابگر تعریف میشود که از انتخابگر مشخصه در هر موقعیتی درون انتخابگر استفاده میکند
- هیچ مشخصه سبک درونخطی نباید در عناصر وجود داشته باشد
- بههیچوجه نباید از انتخابگرهای همنیا استفاده شود. WebCore بهسادگی یک سوئیچ سراسری را هنگام مواجهه با هر انتخابگر همنیا پرتاب میکند و همرسانی سبک را برای کل سند درصورت وجود آنها غیرفعال میکند. این شامل گزینشگر + و گزینشگرهایی مانند :first-child و :last-child میشود.
درخت قانون Firefox
Firefox دو درخت اضافی برای محاسبه آسانتر سبک دارد: درخت قانون و درخت بافت سبک. WebKit نیز اشیای سبک دارد اما آنها در درختی مانند درخت بافت سبک ذخیره نمیشوند، فقط گره DOM به سبک مربوطه خود اشاره میکند.
بافتهای سبک حاوی مقادیر پایانی هستند. این مقادیر با اعمال همه قوانین مطابقت به ترتیب صحیح و انجام دستکاریهایی که آنها را از مقادیر منطقی به مقادیر واقعی تبدیل میکند، محاسبه میشوند. برای مثال، اگر مقدار منطقی درصدی از صفحه باشد، محاسبه میشود و به واحدهای مطلق تبدیل میشود. ایده درخت قانون واقعاً هوشمندانه است. این کار امکان همرسانی این مقادیر را بین گرهها فراهم میکند تا از محاسبه مجدد آنها جلوگیری شود. این کار باعث صرفهجویی در فضا نیز میشود.
همه قوانین مطابقتدادهشده در یک درخت ذخیره میشوند. گرههای پایین در مسیر اولویت بالاتری دارند. این درخت حاوی همه مسیرهای مطابقتهای قانون است که پیدا شدهاند. ذخیره کردن قوانین بهصورت تنبل انجام میشود. درخت در ابتدا برای هر گره محاسبه نمیشود، اما هر زمان که سبک گره نیاز به محاسبه داشته باشد، مسیرهای محاسبهشده به درخت اضافه میشوند.
هدف این است که مسیرهای درخت را بهعنوان واژههایی در واژگان ببینیم. فرض کنید قبلاً این درخت قانون را محاسبه کردهایم:
فرض کنید باید قوانین را برای عنصر دیگری در درخت محتوا مطابقت دهیم و متوجه شویم که قوانین مطابقتیافته (به ترتیب صحیح) B-E-I هستند. ما ازقبل این مسیر را در درخت داریم زیرا قبلاً مسیر A-B-E-I-L را محاسبه کردهایم. اکنون کار کمتری برای انجام دادن خواهیم داشت.
ببینیم درخت چگونه کار ما را آسان میکند.
تقسیم به ساختارها
بافتهای سبک به ساختارها تقسیم میشوند. این ساختارها حاوی اطلاعات سبک برای دستهای خاص مانند حاشیه یا رنگ هستند. همه ویژگیهای موجود در یک ساختار یا موروثی هستند یا غیرموروثی. خصوصیتهای موروثی خصوصیتهایی هستند که اگر توسط عنصر تعریف نشده باشند، از عنصر ولی آن به ارث میرسند. ویژگیهای غیرموروثی (بهنام ویژگیهای «بازنشانی») اگر تعریف نشده باشند از مقادیر پیشفرض استفاده میکنند.
این درخت با ذخیره کردن کل ساختارها (شامل مقادیر نهایی محاسبهشده) در درخت به ما کمک میکند. ایده این است که اگر گره پایینی تعریفی برای ساختار ارائه نکرده باشد، میتوان از ساختار ذخیرهشده در حافظه پنهان در گره بالایی استفاده کرد.
محاسبه زمینههای سبک بااستفاده از درخت قانون
هنگام محاسبه بافت سبک برای یک عنصر خاص، ابتدا مسیری را در درخت قانون محاسبه میکنیم یا از مسیر موجود استفاده میکنیم. سپس شروع به اعمال قوانین در مسیر میکنیم تا ساختارها را در بافت سبک جدیدمان پر کنیم. از گره پایینی مسیر شروع میکنیم - گرهی که بالاترین اولویت را دارد (معمولاً خاصترین انتخابگر) و درخت را تا زمانی که ساختار ما پر شود پیمایش میکنیم. اگر هیچ مشخصاتی برای ساختار در آن گره قانون وجود نداشته باشد، میتوانیم بهطور قابلتوجهی بهینهسازی کنیم - در درخت بالا میرویم تا گرهای را پیدا کنیم که آن را بهطور کامل مشخص میکند و به آن اشاره میکند - این بهترین بهینهسازی است - کل ساختار مشترک است. این کار باعث صرفهجویی در محاسبه مقادیر نهایی و حافظه میشود.
اگر تعریفهای جزئی پیدا کنیم، تا زمانی که ساختار پر شود، درخت را بالا میرویم.
اگر هیچ تعریفی برای ساختارمان پیدا نکردیم، درصورتیکه ساختار از نوع «موروثی» باشد، به ساختار والد در درخت بافت اشاره میکنیم. در این مورد، ما همچنین در همرسانی ساختارها موفق بودیم. اگر ساختار بازنشانی باشد، از مقادیر پیشفرض استفاده خواهد شد.
اگر خاصترین گره مقادیری را اضافه کند، باید محاسبات اضافهای برای تبدیل آن به مقادیر واقعی انجام دهیم. سپس نتیجه را در گره درخت ذخیره میکنیم تا بتواند توسط فرزندان استفاده شود.
درصورتیکه عنصری دارای خواهر یا برادر باشد که به همان گره درختی اشاره میکند، کل بافت سبک میتواند بین آنها همرسانی شود.
بیایید مثالی را ببینیم: فرض کنید این 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}
برای سادهتر کردن کار، فرض کنید فقط باید دو ساختار را پر کنیم: ساختار رنگ و ساختار حاشیه. ساختار رنگ فقط یک عضو دارد: رنگ ساختار حاشیه شامل چهار طرف است.
درخت قانون حاصل به این شکل خواهد بود (گرهها با نام گره علامتگذاری شدهاند: شماره قانونی که به آن اشاره میکنند):
درخت زمینه به این شکل خواهد بود (نام گره: گره قانونی که به آن اشاره میکنند):
فرض کنید HTML را تجزیه میکنیم و به تگ <div> دوم میرسیم. باید برای این گره بافت سبکی ایجاد کنیم و ساختارهای سبک آن را پر کنیم.
قوانین را مطابقت میدهیم و متوجه میشویم که قوانین منطبق برای <div> عبارتاند از ۱، ۲، و ۶.
این یعنی ازقبل مسیری در درخت وجود دارد که عنصر ما میتواند از آن استفاده کند و فقط باید گره دیگری برای قانون ۶ به آن اضافه کنیم (گره F در درخت قانون).
بافتار سبکی ایجاد میکنیم و آن را در درخت بافتار قرار میدهیم. بافت سبک جدید به گره F در درخت قانون اشاره خواهد کرد.
اکنون باید ساختارهای سبک را پر کنیم. با پر کردن ساختار حاشیه شروع میکنیم. ازآنجاییکه آخرین گره قانون (F) به ساختار حاشیه اضافه نمیشود، میتوانیم درخت را بالا برویم تا ساختار ذخیرهشده در حافظه نهان را که در درج گره قبلی محاسبه شده است پیدا کنیم و از آن استفاده کنیم. آن را در گره B پیدا میکنیم که بالاترین گرهای است که قوانین حاشیه را مشخص کرده است.
ما تعریفی برای ساختار رنگ داریم، بنابراین نمیتوانیم از ساختار ذخیرهشده در حافظه نهان استفاده کنیم. ازآنجاییکه رنگ یک مشخصه دارد، لازم نیست برای تکمیل مشخصههای دیگر به بالای درخت برویم. مقدار نهایی را محاسبه میکنیم (تبدیل رشته به RGB و غیره) و ساختار محاسبهشده را در این گره ذخیره میکنیم.
کار روی عنصر <span> دوم حتی آسانتر است. قوانین را مطابقت میدهیم و به این نتیجه میرسیم که این بخش به قانون G اشاره دارد، درست مانند بخش قبلی.
ازآنجاییکه خواهر و برادرهایی داریم که به یک گره اشاره میکنند، میتوانیم کل بافت سبک را همرسانی کنیم و فقط به بافت گستره قبلی اشاره کنیم.
برای ساختارهایی که حاوی قوانینی هستند که از عنصر ولی به ارث برده شدهاند، ذخیرهسازی موقت در درخت زمینه انجام میشود (ویژگی رنگ درواقع به ارث برده میشود، اما Firefox آن را بهعنوان بازنشانیشده درنظر میگیرد و آن را در درخت قانون ذخیره موقت میکند).
برای مثال، اگر قوانینی برای قلمها در پاراگراف اضافه کرده باشیم:
p {font-family: Verdana; font size: 10px; font-weight: bold}
سپس عنصر پاراگراف، که فرزند div در درخت زمینه است، میتوانست همان ساختار قلم والد خود را همرسانی کند. این درصورتی است که هیچ قانون قلمی برای پاراگراف مشخص نشده باشد.
در WebKit که درخت قانون ندارد، اعلانهای منطبق چهار بار پیمایش میشوند. ابتدا ویژگیهای غیرمهم با اولویت بالا اعمال میشوند (ویژگیهایی که باید ابتدا اعمال شوند چون ویژگیهای دیگر به آنها وابسته هستند، مثل نمایش)، سپس ویژگیهای مهم با اولویت بالا، سپس ویژگیهای غیرمهم با اولویت عادی، و درنهایت قوانین مهم با اولویت عادی اعمال میشوند. این یعنی ویژگیهایی که چندین بار ظاهر میشوند طبق ترتیب آبشاری صحیح حلوفصل خواهند شد. آخرین مورد برنده میشود.
بنابراین بهطور خلاصه: همرسانی اشیای سبک (بهطور کامل یا برخیاز ساختارهای درون آنها) مشکلات ۱ و ۳ را حل میکند. درخت قانون Firefox همچنین به اعمال ویژگیها به ترتیب صحیح کمک میکند.
دستکاری قوانین برای مسابقه آسان
چندین منبع برای قوانین سبک وجود دارد:
- قوانین CSS، چه در شیوهنامههای خارجی و چه در عناصر سبک.
css p {color: blue} - مشخصههای سبک بهخط مانند
html <p style="color: blue" /> - مشخصههای بصری HTML (که به قوانین سبک مربوطه نگاشت میشوند)
html <p bgcolor="blue" />دو مورد آخر بهراحتی با عنصر مطابقت داده میشوند زیرا او مالک مشخصههای سبک است و مشخصههای HTML را میتوان بااستفاده از عنصر بهعنوان کلید نگاشت کرد.
همانطور که قبلاً در مشکل شماره ۲ اشاره شد، مطابقت قانون CSS میتواند پیچیدهتر باشد. برای حل این مشکل، قوانین برای دسترسی آسانتر دستکاری میشوند.
پساز تجزیه شیوهنامه، قوانین براساس گزینشگر به یکی از چندین نقشه درهمسازی اضافه میشوند. نقشههایی براساس شناسه، نام کلاس، نام برچسب، و نقشهای کلی برای هر چیزی که در این دستهها قرار نمیگیرد وجود دارد. اگر گزینشگر شناسه باشد، قانون به نگاشت شناسه اضافه خواهد شد، اگر کلاس باشد به نگاشت کلاس اضافه خواهد شد و غیره.
این دستکاری مطابقت دادن قوانین را بسیار آسانتر میکند. نیازی نیست در هر اعلان جستجو کنیم: میتوانیم قوانین مربوط به یک عنصر را از نقشهها استخراج کنیم. این بهینهسازی بیشاز ۹۵٪ از قوانین را حذف میکند، بهطوریکه حتی نیازی به درنظر گرفتن آنها درطول فرایند مطابقت نیست(۴.۱).
برای مثال، قوانین سبک زیر را ببینید:
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 را پیدا کنیم. نقشه کلاس حاوی کلید «خطا» خواهد بود که قانون «p.error» در آن یافت میشود. عنصر div قوانین مربوطه را در نقشه شناسه (کلید همان شناسه است) و نقشه برچسب خواهد داشت. بنابراین تنها کار باقیمانده این است که بفهمیم کدامیک از قوانینی که توسط کلیدها استخراج شدهاند واقعاً مطابقت دارند.
برای مثال، اگر قانون div این بود:
table div {margin: 5px}
این عنصر همچنان از نقشه برچسب استخراج میشود، زیرا کلید راستترین انتخابگر است، اما با عنصر div ما که جدولی ندارد مطابقت نخواهد داشت.
هم WebKit و هم Firefox این دستکاری را انجام میدهند.
ترتیب آبشاری برگه قالببندی
شیء سبک دارای ویژگیهایی است که با هر مشخصه دیداری مطابقت دارد (همه مشخصههای CSS اما عمومیتر). اگر ویژگی توسط هیچیک از قوانین مطابقتیافته تعریف نشده باشد، برخیاز ویژگیها را میتوان از شیء سبک عنصر والد به ارث برد. سایر ویژگیها مقادیر پیشفرض دارند.
مشکل زمانی شروع میشود که بیش از یک تعریف وجود داشته باشد - در اینجا ترتیب آبشاری برای حل مشکل ارائه میشود.
اعلامیه یک ویژگی سبک میتواند در چندین شیوهنامه و چندین بار در یک شیوهنامه ظاهر شود. این یعنی ترتیب اعمال قوانین بسیار مهم است. به این ترتیب، ترتیب «آبشاری» میگویند. طبق مشخصات CSS2، ترتیب آبشاری (از کم به زیاد) عبارت است از:
- بیانیههای مرورگر
- اعلامیههای عادی کاربر
- اظهارنامههای عادی نویسنده
- اظهارنامههای مهم نویسنده
- اعلامیههای مهم کاربر
اعلامیههای مرورگر کمترین اهمیت را دارند و کاربر تنها درصورتی نویسنده را لغو میکند که اعلامیه بهعنوان مهم علامتگذاری شده باشد. اعلامیههایی که ترتیب یکسانی دارند براساس ویژگی و سپس ترتیبی که مشخص شدهاند مرتب میشوند. مشخصههای دیداری HTML به اعلانهای CSS منطبق ترجمه میشوند . این قوانین بهعنوان قوانین نویسنده با اولویت پایین درنظر گرفته میشوند.
ویژگی
ویژگی گزینشگر توسط مشخصات CSS2 بهصورت زیر تعریف میشود:
- اگر اعلانی که از آن است ویژگی «style» باشد و نه قانون با انتخابگر، ۱ حساب کنید، درغیراینصورت ۰ (= a)
- تعداد مشخصههای شناسه را در گزینشگر بشمارید (= b)
- تعداد سایر مشخصهها و شبهکلاسها را در انتخابگر بشمارید (= c)
- تعداد نامهای عنصر و شبهعنصرها در گزینشگر (= d) را بشمارید
الحاق چهار عدد a-b-c-d (در یک سیستم عددی با پایه بزرگ) ویژگی را نشان میدهد.
پایه عددی که باید استفاده کنید براساس بالاترین تعداد در یکی از دستهها تعریف میشود.
برای مثال، اگر a=14 باشد، میتوانید از مبنای شانزدهشانزدهی استفاده کنید. در موارد نادری که a=17 باشد، به پایه عدد ۱۷ رقمی نیاز خواهید داشت. وضعیت دوم میتواند با انتخابگری مانند این اتفاق بیفتد: html body div div p… (۱۷ برچسب در انتخابگر شما… خیلی محتمل نیست).
چند مثال:
* {} /* 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» و «children are dirty» که یعنی اگرچه خود رندرکننده ممکن است مشکلی نداشته باشد، اما حداقل یک فرزند دارد که به چیدمان نیاز دارد.
چیدمان سراسری و افزایشی
چیدمان میتواند در کل درخت رندر راهاندازی شود - این چیدمان «سراسری» است. این اتفاق میتواند درنتیجه موارد زیر رخ دهد:
- تغییر سبک سراسری که بر همه پردازنده تأثیر میگذارد، مثل تغییر اندازه قلم.
- درنتیجه تغییر اندازه صفحه
چیدمان میتواند افزایشی باشد، فقط پردازنده گرافیکی کثیف چیده خواهد شد (این میتواند باعث آسیب شود که به چیدمانهای اضافی نیاز خواهد داشت).
وقتی پردازنده گرافیکی کثیف باشد، چیدمان افزایشی (بهصورت ناهمگام) راهاندازی میشود. برای مثال، وقتی پردازنده جدیدی به درخت پردازش اضافه میشود، پساز اینکه محتوای اضافی از شبکه دریافت شد و به درخت DOM اضافه شد.
چیدمان ناهمگام و همگام
چیدمان افزایشی بهصورت ناهمگام انجام میشود. Firefox «فرمانهای بازچینی» را برای چیدمانهای افزایشی در صف قرار میدهد و یک زمانبند اجرای دستهای این فرمانها را راهاندازی میکند. WebKit همچنین دارای یک تایمر است که یک طرحبندی افزایشی را اجرا میکند - درخت پیمایش میشود و رندرکنندههای «کثیف» طرحبندی میشوند.
دستورگانهایی که اطلاعات سبک را درخواست میکنند، مانند «offsetHeight»، میتوانند چیدمان افزایشی را بهطور همزمان راهاندازی کنند.
چیدمان سراسری معمولاً بهصورت همزمان راهاندازی میشود.
گاهیاوقات، طرحبندی بهعنوان فراخوانی برگشتی پساز طرحبندی اولیه راهاندازی میشود، زیرا برخیاز مشخصهها، مانند موقعیت پیمایش، تغییر کردهاند.
بهینهسازیها
وقتی چیدمانی با «تغییر اندازه» یا تغییر در موقعیت(و نه اندازه) پردازنده راهاندازی میشود، اندازههای پردازنده از حافظه نهان گرفته میشود و دوباره محاسبه نمیشود…
در برخی موارد، فقط یک زیردرخت اصلاح میشود و چیدمان از ریشه شروع نمیشود. این اتفاق در مواردی میتواند روی دهد که تغییر محلی باشد و بر محیط اطرافش تأثیر نگذارد - مثل نوشتاری که در فیلدهای نوشتاری درج میشود (درغیراینصورت، هر کلیدزنی باعث میشود چیدمانی از ریشه شروع شود).
فرایند چیدمان
چیدمان معمولاً الگوی زیر را دارد:
- پردازنده والد عرض خودش را تعیین میکند.
- ولی روی فرزندان میرود و:
- Place the child renderer (sets its x and y).
- درصورت نیاز، طرحبندی فرزند را فراخوانی میکند - آنها کثیف هستند یا ما در طرحبندی سراسری هستیم، یا بهدلیل دیگری - که ارتفاع فرزند را محاسبه میکند.
- ولی از ارتفاع تجمعی فرزندان و ارتفاع حاشیهها و لایهگذاری برای تنظیم ارتفاع خودش استفاده میکند - این ارتفاع توسط ولیِ پردازنده ولی استفاده خواهد شد.
- بیت کثیف آن را روی نادرست تنظیم میکند.
Firefox از شیء «وضعیت» (nsHTMLReflowState) بهعنوان پارامتری برای چیدمان (با اصطلاح «reflow») استفاده میکند. ازجمله موارد دیگر، این وضعیت شامل عرض والد میشود.
خروجی چیدمان Firefox یک شیء «سنجه» (nsHTMLReflowMetrics) است. این ویژگی حاوی ارتفاع محاسبهشده پردازنده خواهد بود.
محاسبه عرض
عرض پردازنده بااستفاده از عرض بلوک محفظه، ویژگی «عرض» سبک پردازنده، حاشیهها، و مرزها محاسبه میشود.
برای مثال، عرض div زیر:
<div style="width: 30%"/>
توسط WebKit بهصورت زیر محاسبه میشود(کلاس RenderBox روش calcWidth):
- عرض محفظه حداکثر availableWidth محفظه و ۰ است. availableWidth در این مورد contentWidth است که بهصورت زیر محاسبه میشود:
clientWidth() - paddingLeft() - paddingRight()
clientWidth و clientHeight نشاندهنده داخل شیء بدون حاشیه و نوار پیمایش است.
عرض عناصر همان مشخصه سبک «عرض» است. این مقدار با محاسبه درصد عرض محفظه بهعنوان مقدار مطلق محاسبه میشود.
حاشیهها و فاصلههای داخلی افقی اکنون اضافه شدهاند.
تا اینجا، این محاسبه «عرض ترجیحی» بود. اکنون حداقل و حداکثر عرض محاسبه خواهد شد.
اگر عرض ترجیحی بیشتر از حداکثر عرض باشد، از حداکثر عرض استفاده میشود. اگر کمتر از حداقل عرض (کوچکترین واحد نشکستنی) باشد، از حداقل عرض استفاده میشود.
مقادیر در حافظه نهان ذخیره میشوند تا درصورت نیاز به چیدمان استفاده شوند، اما عرض تغییر نمیکند.
شکستن خط
وقتی پردازنده در وسط چیدمان تصمیم میگیرد که باید شکسته شود، پردازنده متوقف میشود و به والد چیدمان اعلام میکند که باید شکسته شود. والد رندرکنندههای اضافی را ایجاد میکند و چیدمان را روی آنها فراخوانی میکند.
نقاشی
در مرحله رنگآمیزی، درخت پردازش پیمایش میشود و روش «paint()» پردازشگر برای نمایش محتوا در صفحه فراخوانده میشود. نقاشی از مؤلفه زیرساخت واسط کاربر استفاده میکند.
سراسری و افزایشی
مانند چیدمان، رنگآمیزی نیز میتواند سراسری باشد - کل درخت رنگآمیزی میشود - یا افزایشی. در نقاشی افزایشی، برخیاز پردازنده گرافیکی بهگونهای تغییر میکنند که روی کل درخت تأثیر نمیگذارد. پردازنده تغییریافته مستطیل خود را روی صفحه باطل میکند. این کار باعث میشود سیستمعامل آن را بهعنوان «ناحیه کثیف» ببیند و رویداد «رنگآمیزی» ایجاد کند. سیستمعامل این کار را بهطور هوشمندانه انجام میدهد و چندین منطقه را در یک منطقه ادغام میکند. در Chrome این کار پیچیدهتر است چون پردازشگر در فرایندی متفاوت از فرایند اصلی قرار دارد. Chrome تاحدی رفتار سیستمعامل را شبیهسازی میکند. ارائه به این رویدادها گوش میدهد و پیام را به ریشه پردازش واگذار میکند. درخت پیمایش میشود تا به پردازنده مربوطه برسد. خودش (و معمولاً فرزندانش) را دوباره رنگآمیزی میکند.
ترتیب نقاشی
CSS2 ترتیب فرایند رنگآمیزی را تعریف میکند. این درواقع ترتیبی است که عناصر در زمینههای پشتهسازی پشتهسازی میشوند. این ترتیب بر رنگآمیزی تأثیر میگذارد زیرا پشتهها از پشت به جلو رنگآمیزی میشوند. ترتیب پشتهسازی تولیدکننده تصویر بلوک عبارت است از:
- رنگ پسزمینه
- تصویر پسزمینه
- مرز
- کودکان
- طرح کلی
فهرست نمایش Firefox
Firefox درخت پردازش را بررسی میکند و فهرست نمایشی برای مستطیل رنگشده میسازد. این شامل پردازنده مربوط به مستطیل، در ترتیب رنگآمیزی درست (پسزمینههای پردازندهها، سپس حاشیهها و غیره) است.
به این ترتیب، درخت فقط یکبار برای رنگآمیزی مجدد پیمایش میشود، نه چندینبار - رنگآمیزی همه پسزمینهها، سپس همه تصاویر، سپس همه حاشیهها و غیره.
Firefox با اضافه نکردن عناصری که پنهان خواهند شد، مثل عناصری که کاملاً زیر عناصر مات دیگر قرار دارند، فرایند را بهینهسازی میکند.
فضای ذخیرهسازی مستطیل WebKit
قبلاز رنگآمیزی مجدد، WebKit مستطیل قدیمی را بهعنوان نقشه بیتی ذخیره میکند. سپس فقط تفاوت بین مستطیل جدید و قدیمی را رنگآمیزی میکند.
تغییرات پویا
مرورگرها سعی میکنند در پاسخ به تغییر، حداقل کنشهای ممکن را انجام دهند. بنابراین تغییر رنگ عنصر فقط باعث رنگآمیزی مجدد آن عنصر میشود. تغییرات در موقعیت عنصر باعث چیدمان و رنگآمیزی مجدد عنصر، فرزندان آن و احتمالاً همنیاها میشود. افزودن گره DOM باعث چیدمان و رنگآمیزی مجدد گره میشود. تغییرات عمده، مانند افزایش اندازه قلم عنصر «html»، باعث نامعتبر شدن حافظههای پنهان، طرحبندی مجدد، و رنگآمیزی مجدد کل درخت میشود.
رشتههای موتور پردازنده
موتور پردازش تکرشتهای است. تقریباً همه چیز، بهجز عملیات شبکه، در یک رشته واحد اتفاق میافتد. در Firefox و Safari، این رشته اصلی مرورگر است. در Chrome، این رشته اصلی فرایند برگه است.
عملیات شبکه را میتوان با چندین رشته موازی انجام داد. تعداد اتصالهای موازی محدود است (معمولاً ۲ تا ۶ اتصال).
حلقه رویداد
رشته اصلی مرورگر یک حلقه رویداد است. این یک حلقه بینهایت است که فرایند را زنده نگه میدارد. این رشته منتظر رویدادها (مانند رویدادهای چیدمان و رنگآمیزی) میماند و آنها را پردازش میکند. این کد Firefox برای حلقه رویداد اصلی است:
while (!mExiting)
NS_ProcessNextEvent(thread);
مدل دیداری CSS2
بوم نقاشی
طبق مشخصات CSS2، اصطلاح بوم «فضایی که ساختار قالببندی در آن ارائه میشود» را توصیف میکند: جایی که مرورگر محتوا را رنگآمیزی میکند.
بوم برای هر بُعد فضا بینهایت است، اما مرورگرها عرض اولیه را براساس ابعاد دریچه دید انتخاب میکنند.
طبق www.w3.org/TR/CSS2/zindex.html، اگر بوم در بوم دیگری قرار داشته باشد، شفاف است و اگر در بوم دیگری قرار نداشته باشد، رنگی که مرورگر تعریف کرده است به آن داده میشود.
مدل جعبه CSS
مدل جعبه CSS جعبههای مستطیلی را توصیف میکند که برای عناصر در درخت سند تولید شدهاند و طبق مدل قالببندی بصری چیده شدهاند.
هر کادر دارای یک ناحیه محتوا (مثلاً نوشتار، تصویر، و غیره) و حاشیه داخلی، حاشیه، و ناحیه حاشیه اختیاری است.
هر گره ۰…n از این جعبهها تولید میکند.
همه عناصر دارای ویژگی «نمایش» هستند که نوع چارگوشی را که تولید خواهد شد تعیین میکند.
مثالها:
block: generates a block box.
inline: generates one or more inline boxes.
none: no box is generated.
پیشفرض درونخطی است اما برگه سبک مرورگر ممکن است پیشفرضهای دیگری را تنظیم کند. برای مثال: نمایش پیشفرض برای عنصر «div» بلوک است.
میتوانید نمونهای از برگه سبک پیشفرض را در اینجا پیدا کنید: www.w3.org/TR/CSS2/sample.html.
طرح موقعیتیابی
سه طرح وجود دارد:
- عادی: شیء براساس جایگاهش در سند قرار میگیرد. این یعنی جای آن در درخت رندر مانند جای آن در درخت DOM است و براساس نوع و ابعاد جعبه آن چیده شده است
- شناور: شیء ابتدا مانند جریان عادی چیده میشود، سپس تا حد امکان به چپ یا راست منتقل میشود
- مطلق: شیء در درخت پردازش در مکانی متفاوت از درخت DOM قرار میگیرد
طرح موقعیتیابی با ویژگی «موقعیت» و مشخصه «شناور» تنظیم میشود.
- static و relative باعث ایجاد جریان عادی میشوند
- مطلق و ثابت باعث جایگاهسازی مطلق میشوند
در موقعیتیابی ایستا، هیچ موقعیتی تعریف نشده است و از موقعیتیابی پیشفرض استفاده میشود. در طرحهای دیگر، نویسنده موقعیت را مشخص میکند: بالا، پایین، چپ، راست.
نحوه چیدمان کادر براساس موارد زیر تعیین میشود:
- نوع جعبه
- ابعاد جعبه
- طرح موقعیتیابی
- اطلاعات خارجی مانند اندازه تصویر و اندازه صفحهنمایش
انواع جعبه
جعبه بلوک: بلوکی را تشکیل میدهد - مستطیل خودش را در پنجره مرورگر دارد.
جعبه درونخطی: بلوک خودش را ندارد، اما درون یک بلوک دربرگیرنده است.
بلوکها بهصورت عمودی یکی پساز دیگری قالببندی میشوند. درونخطیها بهصورت افقی قالببندی میشوند.
چارگوشهای درونخطی داخل خطوط یا «چارگوشهای خطی» قرار میگیرند. خطوط حداقل بهاندازه بلندترین چارگوش هستند اما میتوانند بلندتر باشند، وقتی چارگوشها «خط مبنا» تراز شده باشند - یعنی بخش پایینی یک عنصر در نقطهای از چارگوش دیگر تراز شده باشد که پایین آن نیست. اگر عرض محفظه کافی نباشد، موارد درونخطی در چند خط قرار میگیرند. این معمولاً همان چیزی است که در یک پاراگراف اتفاق میافتد.
موقعیتیابی
خویشاوند
موقعیتیابی نسبی - مانند معمول موقعیتیابی میشود و سپس با دلتای موردنیاز جابهجا میشود.
شناورها
جعبه شناور به سمت چپ یا راست خط منتقل میشود. ویژگی جالب این است که جعبههای دیگر در اطراف آن جریان دارند. HTML:
<p>
<img style="float: right" src="images/image.gif" width="100" height="100">
Lorem ipsum dolor sit amet, consectetuer...
</p>
به این شکل خواهد بود:
مطلق و ثابت
چیدمان دقیقاً بدون توجه به جریان عادی تعریف میشود. این عنصر در جریان عادی شرکت نمیکند. ابعاد نسبت به محتوا است. در حالت ثابت، دربرگیرنده همان ناحیه نمایش است.
نمایش لایهای
این مورد با ویژگی CSS z-index مشخص میشود. این مقدار نشاندهنده بُعد سوم چارگوش است: موقعیت آن در امتداد «محور 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>
نتیجه این خواهد بود:
اگرچه div قرمز در نشانهگذاری قبلاز div سبز قرار دارد و در جریان عادی قبلاز آن رنگآمیزی میشد، ویژگی z-index بالاتر است، بنابراین در پشتهای که توسط جعبه ریشه نگهداری میشود، جلوتر است.
منابع
معماری مرورگر
- Grosskurth, Alan. معماری مرجع برای مرورگرهای وب (pdf)
- Gupta, Vineet. نحوه عملکرد مرورگرها - بخش ۱ - معماری
تجزیه کردن
- Aho, Sethi, Ullman, Compilers: Principles, Techniques, and Tools (aka the "Dragon book"), Addison-Wesley, 1986
- ریک جلیف. The Bold and the Beautiful: two new drafts for HTML 5.
Firefox
- چپ دیوید بارون، HTML و CSS سریعتر: موتور چیدمان داخلی برای توسعهدهندگان وب.
- چپ دیوید بارون، HTML و CSS سریعتر: موتور چیدمان داخلی برای توسعهدهندگان وب (ویدیو گفتگوی فنی Google)
- چپ دیوید بارون، موتور چیدمان Mozilla
- چپ دیوید بارون، مستندات سیستم سبک Mozilla
- کریس واترسون، یادداشتهایی درباره بازآرایی HTML
- کریس واترسون، نمای کلی Gecko
- الکساندر لارسون، زندگی یک درخواست اچتیتیپی HTML
WebKit
- دیوید هایت، پیادهسازی CSS(بخش ۱)
- دیوید هایت، نمای کلی WebCore
- دیوید هایت، WebCore Rendering
- دیوید هایت، مشکل FOUC
مشخصات W3C
دستورالعملهای ساخت مرورگرها
ترجمهها
این صفحه دو بار به ژاپنی ترجمه شده است:
- How Browsers Work - Behind the Scenes of Modern Web Browsers (ja) by @kosei
- ブラウザってどうやって動いてるの?(モダンWEBブラウザシーンの裏側 by @ikeike443 and @kiyoto01.
میتوانید ترجمههای میزبانیشده خارجی کرهای و ترکی را مشاهده کنید.
از همه سپاسگزاریم!