پشت صحنه مرورگرهای وب مدرن
پیشگفتار
این آغازگر جامع در مورد عملیات داخلی WebKit و Gecko نتیجه تحقیقات زیادی است که توسط توسعه دهنده اسرائیلی Tali Garsiel انجام شده است. در طی چند سال، او تمام داده های منتشر شده در مورد داخلی مرورگر را بررسی کرد و زمان زیادی را صرف خواندن کد منبع مرورگر وب کرد. او نوشت:
به عنوان یک توسعهدهنده وب، یادگیری اجزای داخلی عملیات مرورگر به شما کمک میکند تا تصمیمهای بهتری بگیرید و توجیهات پشت بهترین شیوههای توسعه را بدانید . در حالی که این یک سند نسبتا طولانی است، توصیه می کنیم مدتی را صرف کند و کاو در آن کنید. از این کار خوشحال خواهید شد.
Paul Irish, Chrome Developer Relations
مقدمه
مرورگرهای وب پرکاربردترین نرم افزارها هستند. در این پرایمر نحوه عملکرد آنها در پشت صحنه را توضیح می دهم. ما خواهیم دید که وقتی google.com
در نوار آدرس تایپ می کنید چه اتفاقی می افتد تا زمانی که صفحه Google را در صفحه مرورگر مشاهده کنید.
مرورگرهایی که در مورد آنها صحبت خواهیم کرد
امروزه پنج مرورگر اصلی روی دسکتاپ استفاده می شود: کروم، اینترنت اکسپلورر، فایرفاکس، سافاری و اپرا. در موبایل، مرورگرهای اصلی عبارتند از مرورگر اندروید، آیفون، اپرا مینی و اپرا موبایل، مرورگر UC، مرورگرهای نوکیا S40/S60 و کروم که همه آنها به جز مرورگرهای اپرا بر پایه WebKit هستند. من مثال هایی از مرورگرهای منبع باز فایرفاکس و کروم و سافاری (که تا حدی منبع باز است) می زنم. طبق آمار StatCounter (تا ژوئن 2013)، کروم، فایرفاکس و سافاری حدود 71 درصد از مرورگرهای دسکتاپ جهانی را تشکیل می دهند. در تلفن همراه، مرورگر اندروید، آیفون و کروم حدود 54 درصد از استفاده را تشکیل می دهند.
عملکرد اصلی مرورگر
عملکرد اصلی یک مرورگر این است که منبع وب را که انتخاب می کنید، با درخواست از سرور و نمایش آن در پنجره مرورگر، ارائه دهد. منبع معمولاً یک سند HTML است، اما ممکن است یک PDF، تصویر یا نوع دیگری از محتوا نیز باشد. مکان منبع توسط کاربر با استفاده از URI (شناسه منبع یکسان) مشخص می شود.
نحوه تفسیر و نمایش فایل های HTML توسط مرورگر در مشخصات HTML و CSS مشخص شده است. این مشخصات توسط سازمان W3C (کنسرسیوم وب جهانی) که سازمان استانداردهای وب است حفظ می شود. برای سالها مرورگرها تنها با بخشی از مشخصات مطابقت داشتند و پسوندهای خود را توسعه دادند. این باعث مشکلات جدی سازگاری برای نویسندگان وب شد. امروزه اکثر مرورگرها کم و بیش با مشخصات مطابقت دارند.
رابط های کاربری مرورگرها شباهت های زیادی با یکدیگر دارند. از جمله عناصر رابط کاربری رایج عبارتند از:
- نوار آدرس برای درج URI
- دکمه های عقب و جلو
- گزینه های نشانک گذاری
- دکمه های بازخوانی و توقف برای بازخوانی یا توقف بارگیری اسناد فعلی
- دکمه صفحه اصلی که شما را به صفحه اصلی خود می برد
به اندازه کافی عجیب، رابط کاربری مرورگر در هیچ مشخصات رسمی مشخص نشده است، فقط از شیوه های خوب شکل گرفته در طول سال ها تجربه و با تقلید مرورگرها از یکدیگر ناشی می شود. مشخصات HTML5 عناصر رابط کاربری را که مرورگر باید داشته باشد تعریف نمی کند، اما برخی از عناصر رایج را فهرست می کند. از جمله آنها می توان به نوار آدرس، نوار وضعیت و نوار ابزار اشاره کرد. البته ویژگی هایی منحصر به فرد برای یک مرورگر خاص مانند مدیریت دانلود فایرفاکس وجود دارد.
زیرساخت های سطح بالا
اجزای اصلی مرورگر عبارتند از:
- رابط کاربری : این شامل نوار آدرس، دکمه برگشت/به جلو، منوی نشانه گذاری، و غیره است. هر قسمت از مرورگر به جز پنجره ای که صفحه درخواستی را می بینید، نمایش داده می شود.
- موتور مرورگر : اقدامات بین رابط کاربری و موتور رندر را به نمایش می گذارد.
- موتور رندر : مسئول نمایش محتوای درخواستی است. به عنوان مثال اگر محتوای درخواستی HTML باشد، موتور رندر HTML و CSS را تجزیه می کند و محتوای تجزیه شده را روی صفحه نمایش می دهد.
- شبکه سازی : برای تماس های شبکه مانند درخواست های HTTP، استفاده از پیاده سازی های مختلف برای پلتفرم های مختلف در پشت یک رابط مستقل از پلت فرم.
- باطن UI : برای ترسیم ویجت های اولیه مانند جعبه های ترکیبی و ویندوز استفاده می شود. این باطن یک رابط عمومی را نشان می دهد که مختص پلتفرم نیست. در زیر آن از روش های رابط کاربری سیستم عامل استفاده می کند.
- مفسر جاوا اسکریپت برای تجزیه و اجرای کد جاوا اسکریپت استفاده می شود.
- ذخیره سازی داده ها . این یک لایه ماندگاری است. ممکن است مرورگر نیاز به ذخیره انواع دادهها به صورت محلی داشته باشد، مانند کوکیها. مرورگرها همچنین از مکانیسم های ذخیره سازی مانند localStorage، IndexedDB، WebSQL و FileSystem پشتیبانی می کنند.
توجه به این نکته مهم است که مرورگرهایی مانند کروم چندین نمونه از موتور رندر را اجرا می کنند: یکی برای هر تب. هر تب در یک فرآیند جداگانه اجرا می شود.
موتورهای رندر
مسئولیت موتور رندر خوب است... رندر، یعنی نمایش محتوای درخواستی در صفحه مرورگر.
به طور پیش فرض موتور رندر می تواند اسناد و تصاویر HTML و XML را نمایش دهد. این می تواند انواع دیگری از داده ها را از طریق پلاگین یا افزونه نمایش دهد. به عنوان مثال، نمایش اسناد PDF با استفاده از یک افزونه نمایش PDF. با این حال، در این فصل ما بر روی مورد اصلی تمرکز خواهیم کرد: نمایش HTML و تصاویری که با استفاده از CSS فرمت شده اند.
مرورگرهای مختلف از موتورهای رندر متفاوتی استفاده می کنند: اینترنت اکسپلورر از Trident، فایرفاکس از Gecko، Safari از WebKit استفاده می کند. کروم و اپرا (از نسخه 15) از Blink، فورک WebKit استفاده می کنند.
WebKit یک موتور رندر متن باز است که به عنوان موتوری برای پلتفرم لینوکس شروع شد و توسط اپل برای پشتیبانی از مک و ویندوز اصلاح شد.
جریان اصلی
موتور رندر شروع به دریافت محتویات سند درخواستی از لایه شبکه می کند. این معمولاً در قطعات 8 کیلوبایتی انجام می شود.
پس از آن، این جریان اصلی موتور رندر است:
موتور رندر شروع به تجزیه سند HTML می کند و عناصر را به گره های DOM در درختی به نام "درخت محتوا" تبدیل می کند. موتور دادههای سبک را هم در فایلهای CSS خارجی و هم در عناصر سبک تجزیه میکند. اطلاعات سبک همراه با دستورالعمل های بصری در HTML برای ایجاد درخت دیگری استفاده می شود: درخت رندر .
درخت رندر شامل مستطیل هایی با ویژگی های بصری مانند رنگ و ابعاد است. مستطیل ها به ترتیبی هستند که روی صفحه نمایش داده می شوند.
پس از ساخت درخت رندر، فرآیند " layout " را طی می کند. این به این معنی است که به هر گره مختصات دقیق جایی که باید روی صفحه نمایش داده شود، بدهید. مرحله بعدی رنگ آمیزی است - درخت رندر پیمایش می شود و هر گره با استفاده از لایه باطن UI نقاشی می شود.
درک این نکته مهم است که این یک روند تدریجی است. برای تجربه کاربری بهتر، موتور رندر سعی می کند در اسرع وقت محتویات را روی صفحه نمایش دهد. قبل از شروع ساختن و چیدمان درخت رندر، منتظر نمی ماند تا تمام HTML تجزیه شود. بخشهایی از محتوا تجزیه و نمایش داده میشود، در حالی که این روند با بقیه مطالبی که از شبکه میآیند ادامه مییابد.
نمونه های جریان اصلی
از شکلهای 3 و 4 میبینید که اگرچه WebKit و Gecko از اصطلاحات کمی متفاوت استفاده میکنند، جریان اساساً یکسان است.
Gecko درخت عناصر با فرمت بصری را "درخت قاب" می نامد. هر عنصر یک قاب است. WebKit از اصطلاح "Render Tree" استفاده می کند و از "Render Objects" تشکیل شده است. WebKit از اصطلاح "layout" برای قرار دادن عناصر استفاده می کند، در حالی که Gecko آن را "Reflow" می نامد. "ضمیمه" اصطلاح WebKit برای اتصال گره های DOM و اطلاعات بصری برای ایجاد درخت رندر است. یک تفاوت جزئی غیر معنایی این است که Gecko یک لایه اضافی بین HTML و درخت DOM دارد. به آن "حضور محتوا" می گویند و کارخانه ای برای ساخت عناصر DOM است. ما در مورد هر بخش از جریان صحبت خواهیم کرد:
تجزیه - عمومی
از آنجایی که تجزیه یک فرآیند بسیار مهم در موتور رندر است، ما کمی عمیق تر به آن خواهیم پرداخت. بیایید با یک مقدمه کوچک در مورد تجزیه شروع کنیم.
تجزیه یک سند به معنای ترجمه آن به ساختاری است که کد می تواند از آن استفاده کند. نتیجه تجزیه معمولاً درختی از گره ها است که ساختار سند را نشان می دهد. این درخت تجزیه یا درخت نحو نامیده می شود.
به عنوان مثال، تجزیه عبارت 2 + 3 - 1
می تواند این درخت را برگرداند:
گرامر
تجزیه بر اساس قوانین نحوی است که سند از آنها پیروی می کند: زبان یا قالبی که با آن نوشته شده است. هر قالبی که می توانید تجزیه کنید باید دارای دستور زبان قطعی متشکل از قواعد واژگان و نحو باشد. به آن گرامر آزاد متن می گویند. زبانهای انسانی چنین زبانهایی نیستند و بنابراین نمیتوان آنها را با تکنیکهای تجزیه مرسوم تجزیه کرد.
تجزیه کننده - ترکیب لکسر
تجزیه را می توان به دو فرآیند فرعی تقسیم کرد: تحلیل واژگانی و تحلیل نحو.
تحلیل واژگانی فرآیندی است که ورودی را به توکن ها تبدیل می کند. نشانه ها واژگان زبان هستند: مجموعه ای از بلوک های سازنده معتبر. در زبان انسان شامل تمام کلماتی است که در فرهنگ لغت برای آن زبان آمده است.
تحلیل نحوی به کارگیری قواعد نحوی زبان است.
تجزیهکنندهها معمولاً کار را بین دو جزء تقسیم میکنند: lexer (گاهی اوقات به آن نشانهساز میگویند) که مسئول شکستن ورودی به نشانههای معتبر است و تجزیهکنندهای که مسئول ساخت درخت تجزیه با تجزیه و تحلیل ساختار سند بر اساس قوانین نحوی زبان است.
lexer می داند که چگونه کاراکترهای نامربوط مانند فاصله های سفید و شکستن خط را از بین ببرد.
فرآیند تجزیه تکراری است. تجزیه کننده معمولاً از lexer یک نشانه جدید می خواهد و سعی می کند نشانه را با یکی از قوانین نحوی مطابقت دهد. اگر یک قانون مطابقت داشته باشد، یک گره مربوط به نشانه به درخت تجزیه اضافه می شود و تجزیه کننده توکن دیگری را می خواهد.
اگر هیچ قاعدهای مطابقت نداشته باشد، تجزیهکننده توکن را در داخل ذخیره میکند و به درخواست توکنها ادامه میدهد تا زمانی که قاعدهای مطابق با تمام نشانههای ذخیرهشده داخلی پیدا شود. اگر قاعده ای پیدا نشد، تجزیه کننده یک استثنا ایجاد می کند. این بدان معناست که سند معتبر نبوده و حاوی خطاهای نحوی است.
ترجمه
در بسیاری از موارد درخت تجزیه محصول نهایی نیست. تجزیه اغلب در ترجمه استفاده می شود: تبدیل سند ورودی به قالب دیگری. یک نمونه تلفیقی است. کامپایلری که کد منبع را به کد ماشین کامپایل می کند، ابتدا آن را به یک درخت تجزیه تجزیه می کند و سپس درخت را به یک سند کد ماشین ترجمه می کند.
مثال تجزیه
در شکل 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
را به عنوان یک عبارت شناسایی می کند (فرایند شناسایی عبارت تکامل می یابد، با قوانین دیگر مطابقت دارد، اما نقطه شروع قانون بالاترین سطح است).
تجزیه کننده پایین به بالا ورودی را اسکن می کند تا زمانی که یک قانون مطابقت داشته باشد. سپس ورودی مطابق با قانون جایگزین می شود. این کار تا پایان ورودی ادامه خواهد داشت. عبارت تا حدی مطابقت شده در پشته تجزیه کننده قرار می گیرد.
این نوع تجزیه کننده از پایین به بالا، تجزیه کننده شیفت-کاهش نامیده می شود، زیرا ورودی به سمت راست منتقل می شود (تصور کنید یک اشاره گر ابتدا به شروع ورودی اشاره می کند و به سمت راست حرکت می کند) و به تدریج به قوانین نحوی کاهش می یابد.
تولید تجزیه کننده به صورت خودکار
ابزارهایی وجود دارند که می توانند تجزیه کننده تولید کنند. شما آنها را با دستور زبان خود تغذیه می کنید - واژگان و قواعد نحوی آن - و آنها یک تجزیه کننده فعال تولید می کنند. ایجاد یک تجزیه کننده نیاز به درک عمیق از تجزیه دارد و ایجاد یک تجزیه کننده بهینه شده با دست آسان نیست، بنابراین مولدهای تجزیه کننده می توانند بسیار مفید باشند.
WebKit از دو مولد تجزیه کننده معروف استفاده می کند: Flex برای ایجاد یک lexer و 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 زیر ترجمه می شود:
مانند 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 را نمی توان با استفاده از تجزیه کننده های معمولی بالا به پایین یا پایین به بالا تجزیه کرد.
دلایل آن عبارتند از:
- ماهیت بخشنده زبان.
- این واقعیت که مرورگرها تحمل خطای سنتی برای پشتیبانی از موارد شناخته شده HTML نامعتبر دارند.
- فرآیند تجزیه مجدد وارد می شود. برای زبانهای دیگر، منبع در طول تجزیه تغییر نمیکند، اما در HTML، کد پویا (مانند عناصر اسکریپت حاوی فراخوانهای
document.write()
) میتواند نشانههای اضافی اضافه کند، بنابراین فرآیند تجزیه در واقع ورودی را تغییر میدهد.
مرورگرها قادر به استفاده از تکنیک های تجزیه معمولی نیستند، تجزیه کننده های سفارشی برای تجزیه HTML ایجاد می کنند.
الگوریتم تجزیه با مشخصات HTML5 به تفصیل شرح داده شده است . الگوریتم شامل دو مرحله است: نشانه گذاری و ساخت درخت.
توکنسازی، تحلیل واژگانی است که ورودی را به نشانهها تجزیه میکند. در میان نشانه های HTML، تگ های شروع، تگ های پایانی، نام ویژگی ها و مقادیر ویژگی ها هستند.
توکنایزر توکن را می شناسد، آن را به سازنده درخت می دهد و کاراکتر بعدی را برای تشخیص نشانه بعدی مصرف می کند و تا پایان ورودی به همین ترتیب ادامه می دهد.
الگوریتم توکن سازی
خروجی الگوریتم یک توکن HTML است. الگوریتم به عنوان یک ماشین حالت بیان می شود. هر حالت یک یا چند کاراکتر از جریان ورودی را مصرف می کند و وضعیت بعدی را با توجه به آن کاراکترها به روز می کند. این تصمیم تحت تأثیر وضعیت نمادسازی فعلی و وضعیت ساخت درخت است. این بدان معناست که همان کاراکتر مصرف شده، بسته به وضعیت فعلی، نتایج متفاوتی را برای حالت بعدی صحیح به همراه خواهد داشت. الگوریتم برای توصیف کامل بسیار پیچیده است، بنابراین بیایید یک مثال ساده را ببینیم که به ما در درک اصل کمک می کند.
مثال اصلی - توکن کردن HTML زیر:
<html>
<body>
Hello world
</body>
</html>
حالت اولیه "وضعیت داده" است. هنگامی که با کاراکتر <
مواجه میشوید، وضعیت به "Tag open state" تغییر میکند. مصرف یک کاراکتر az
باعث ایجاد یک "Start tag token" می شود، وضعیت به "Tag name state" تغییر می کند. تا زمانی که کاراکتر >
مصرف شود در این حالت می مانیم. هر کاراکتر به نام رمز جدید اضافه می شود. در مورد ما توکن ایجاد شده یک توکن html
است.
وقتی به تگ >
رسید، نشانه فعلی منتشر می شود و وضعیت به "وضعیت داده" تغییر می کند. تگ <body>
نیز با همین مراحل درمان می شود. تا کنون تگ های html
و body
منتشر شده است. اکنون به "وضعیت داده" بازگشته ایم. مصرف کاراکتر H
از Hello world
باعث ایجاد و انتشار یک نشانه کاراکتر می شود، این کار تا رسیدن به <
از </body>
ادامه می یابد. ما برای هر شخصیت Hello world
یک نشانه کاراکتر منتشر می کنیم.
اکنون به "حالت باز برچسب" بازگشته ایم. مصرف ورودی بعدی /
باعث ایجاد یک end tag token
و انتقال به "وضعیت نام برچسب" می شود. دوباره در این حالت می مانیم تا زمانی که به >
برسیم. سپس نشانه تگ جدید منتشر می شود و به "وضعیت داده" برمی گردیم. با ورودی </html>
مانند مورد قبلی رفتار می شود.
الگوریتم ساخت درخت
هنگامی که تجزیه کننده ایجاد می شود، شی Document ایجاد می شود. در مرحله ساخت درخت، درخت DOM با سند در ریشه آن اصلاح می شود و عناصر به آن اضافه می شود. هر گره منتشر شده توسط توکنایزر توسط سازنده درخت پردازش می شود. برای هر توکن، مشخصات مشخص میکند که کدام عنصر DOM مربوط به آن است و برای این توکن ایجاد میشود. عنصر به درخت DOM و همچنین پشته عناصر باز اضافه می شود. این پشته برای تصحیح عدم تطابق تودرتو و تگ های بسته نشده استفاده می شود. این الگوریتم همچنین به عنوان یک ماشین حالت توصیف می شود. حالت ها «حالت های درج» نامیده می شوند.
بیایید فرآیند ساخت درخت را برای ورودی مثال ببینیم:
<html>
<body>
Hello world
</body>
</html>
ورودی مرحله ساخت درخت، دنباله ای از نشانه ها از مرحله توکن سازی است. حالت اول "حالت اولیه" است. دریافت توکن "html" باعث انتقال به حالت "قبل از html" و پردازش مجدد رمز در آن حالت می شود. این باعث ایجاد عنصر HTMLHtmlElement می شود که به شی root Document اضافه می شود.
وضعیت به "قبل از سر" تغییر خواهد کرد. سپس نشانه "body" دریافت می شود. یک HTMLHeadElement به طور ضمنی ایجاد میشود، اگرچه ما یک نشانه "head" نداریم و به درخت اضافه میشود.
اکنون به حالت "in head" و سپس به "after head" می رویم. توکن بدنه دوباره پردازش میشود، یک HTMLBodyElement ایجاد و درج میشود و حالت به "in body" منتقل میشود.
نمادهای کاراکتر رشته "Hello world" اکنون دریافت شده است. اولین مورد باعث ایجاد و درج یک گره "متن" می شود و کاراکترهای دیگر به آن گره اضافه می شوند.
دریافت نشانه پایان بدنه باعث انتقال به حالت "after body" می شود. اکنون تگ پایان html را دریافت می کنیم که ما را به حالت "after after body" منتقل می کند. دریافت نشانه پایان فایل، تجزیه را پایان می دهد.
اقدامات زمانی که تجزیه به پایان رسید
در این مرحله، مرورگر سند را بهعنوان تعاملی علامتگذاری میکند و شروع به تجزیه اسکریپتهایی میکند که در حالت "به تعویق افتاده" هستند: آنهایی که باید پس از تجزیه سند اجرا شوند. سپس حالت سند روی "کامل" تنظیم می شود و رویداد "بار" فعال می شود.
می توانید الگوریتم های کامل برای توکن سازی و ساخت درخت را در مشخصات HTML5 مشاهده کنید.
تحمل خطای مرورگرها
شما هرگز خطای "Invalid Syntax" را در صفحه 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>
استفاده می کنند. به منظور سازگاری با اینترنت اکسپلورر و فایرفاکس، 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 یا بدنه نابجا
باز هم - نظر برای خود صحبت می کند.
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 مخفف فضای سفید است). یک مجموعه قوانین شامل پرانتزهای فرفری و داخل آنها یک اعلان یا به صورت اختیاری تعدادی اعلان است که با یک نقطه ویرگول از هم جدا شده اند. "اعلامیه" و "انتخاب کننده" در تعاریف BNF زیر تعریف خواهند شد.
تجزیه کننده CSS WebKit
WebKit از ژنراتورهای تجزیه کننده Flex و Bison برای ایجاد تجزیه کننده به طور خودکار از فایل های دستور زبان CSS استفاده می کند. همانطور که از مقدمه تجزیه کننده به یاد می آورید، Bison یک تجزیه کننده کاهش شیفت از پایین به بالا ایجاد می کند. فایرفاکس از تجزیه کننده بالا به پایین استفاده می کند که به صورت دستی نوشته شده است. در هر دو مورد، هر فایل CSS در یک شیء StyleSheet تجزیه می شود. هر شی شامل قوانین CSS است. اشیاء قانون CSS حاوی اشیاء انتخابگر و اعلان و سایر اشیاء مربوط به دستور زبان CSS است.
پردازش سفارش برای اسکریپت ها و شیوه نامه ها
اسکریپت ها
مدل وب همگام است. نویسندگان انتظار دارند اسکریپت ها بلافاصله پس از رسیدن تجزیه کننده به تگ <script>
تجزیه و اجرا شوند. تجزیه سند تا زمانی که اسکریپت اجرا نشود متوقف می شود. اگر اسکریپت خارجی است، ابتدا منبع باید از شبکه واکشی شود - این کار به صورت همزمان انجام می شود و تجزیه تا زمانی که منبع واکشی شود متوقف می شود. این مدل سالها بود و در مشخصات HTML4 و 5 نیز مشخص شده است. نویسندگان میتوانند صفت «defer» را به یک اسکریپت اضافه کنند، در این صورت تجزیه سند متوقف نمیشود و پس از تجزیه سند اجرا میشود. HTML5 گزینهای را برای علامتگذاری اسکریپت بهعنوان ناهمزمان اضافه میکند تا توسط یک رشته دیگر تجزیه و اجرا شود.
تجزیه نظری
هم WebKit و هم Firefox این بهینه سازی را انجام می دهند. در حین اجرای اسکریپت ها، رشته دیگری بقیه سند را تجزیه می کند و متوجه می شود که چه منابع دیگری باید از شبکه بارگیری شود و آنها را بارگذاری می کند. به این ترتیب می توان منابع را روی اتصالات موازی بارگذاری کرد و سرعت کلی بهبود یافت. توجه: تجزیه کننده حدسی فقط ارجاع به منابع خارجی مانند اسکریپت های خارجی، شیوه نامه ها و تصاویر را تجزیه می کند: درخت DOM را تغییر نمی دهد - که به تجزیه کننده اصلی واگذار می شود.
برگه های سبک
از طرف دیگر استایل شیت ها مدل متفاوتی دارند. از لحاظ مفهومی به نظر می رسد که از آنجایی که شیوه نامه ها درخت DOM را تغییر نمی دهند، دلیلی وجود ندارد که منتظر آنها باشیم و تجزیه سند را متوقف کنیم. با این حال، مشکلی وجود دارد که اسکریپت ها اطلاعات سبک را در مرحله تجزیه سند می خواهند. اگر استایل هنوز بارگذاری و تجزیه نشده باشد، اسکریپت پاسخ های اشتباه دریافت می کند و ظاهراً این باعث مشکلات زیادی شده است. به نظر می رسد یک مورد لبه است اما بسیار رایج است. فایرفاکس همه اسکریپت ها را وقتی که یک شیوه نامه وجود دارد که هنوز در حال بارگذاری و تجزیه است، مسدود می کند. WebKit تنها زمانی اسکریپت ها را مسدود می کند که سعی کنند به ویژگی های سبک خاصی دسترسی پیدا کنند که ممکن است توسط شیوه نامه های بارگیری نشده تحت تأثیر قرار گیرند.
رندر ساخت و ساز درخت
در حالی که درخت DOM در حال ساخت است، مرورگر درخت دیگری به نام درخت رندر می سازد. این درخت از عناصر بصری به ترتیب نمایش داده می شود. این نمایش تصویری سند است. هدف این درخت این است که امکان رنگ آمیزی محتویات به ترتیب صحیح آنها را فراهم کند.
فایرفاکس عناصر موجود در درخت رندر را فریم می نامد. WebKit از اصطلاح renderer یا render object استفاده می کند.
یک رندر می داند چگونه خود و فرزندانش را چیدمان و نقاشی کند.
کلاس 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 غیر بصری در درخت رندر درج نمی شوند. یک مثال عنصر "سر" است. همچنین عناصری که مقدار نمایش آنها به "none" اختصاص داده شده است، در درخت ظاهر نمی شوند (در حالی که عناصر با نمای "پنهان" در درخت ظاهر می شوند).
عناصر DOM وجود دارد که با چندین شیء بصری مطابقت دارد. اینها معمولاً عناصری با ساختار پیچیده هستند که با یک مستطیل قابل توصیف نیستند. به عنوان مثال، عنصر "انتخاب" سه رندر دارد: یکی برای ناحیه نمایش، یکی برای کادر لیست کشویی و دیگری برای دکمه. همچنین هنگامی که متن به چند خط شکسته می شود زیرا عرض برای یک خط کافی نیست، خطوط جدید به عنوان رندر اضافی اضافه می شوند.
نمونه دیگری از رندرهای متعدد، HTML شکسته است. طبق مشخصات CSS، یک عنصر درون خطی باید فقط شامل عناصر بلوک یا فقط عناصر درون خطی باشد. در مورد محتوای مختلط، رندرهای بلوک ناشناس برای بسته بندی عناصر درون خطی ایجاد خواهند شد.
برخی از اشیاء رندر با یک گره DOM مطابقت دارند اما در همان مکان درخت نیستند. شناورها و عناصر کاملاً قرار گرفته خارج از جریان هستند، در قسمت دیگری از درخت قرار می گیرند و به قاب واقعی نگاشت می شوند. قاب نگهدارنده مکان جایی است که باید میبودند.
جریان ساخت درخت
در فایرفاکس، ارائه به عنوان شنونده برای به روز رسانی های DOM ثبت می شود. ارائه ایجاد فریم را به FrameConstructor
واگذار می کند و سازنده سبک را حل می کند (به محاسبه سبک مراجعه کنید) و یک فریم ایجاد می کند.
در WebKit به فرآیند حل استایل و ایجاد رندر «پیوست» می گویند. هر گره DOM دارای یک روش "اتصال" است. پیوست سنکرون است، درج گره به درخت DOM، گره جدید را متد «پیوست» می نامد.
پردازش تگ های html و body منجر به ساخت ریشه درخت رندر می شود. شیء رندر ریشه مطابق با چیزی است که مشخصات CSS آن را بلوک حاوی می نامد: بالاترین بلوک که شامل تمام بلوک های دیگر است. ابعاد آن عبارتند از viewport: پنجره مرورگر ابعاد منطقه را نمایش می دهد. فایرفاکس آن را ViewPortFrame
و WebKit آن را RenderView
می نامد. این شی رندر است که سند به آن اشاره می کند. بقیه درخت به عنوان یک درج گره های DOM ساخته شده است.
مشخصات CSS2 در مدل پردازش را ببینید.
محاسبه سبک
ساخت درخت رندر مستلزم محاسبه خواص بصری هر شی رندر است. این کار با محاسبه ویژگی های سبک هر عنصر انجام می شود.
این سبک شامل برگههای سبک با ریشههای مختلف، عناصر سبک درون خطی و ویژگیهای بصری در HTML است (مانند ویژگی "bgcolor"). این سبک به ویژگیهای سبک CSS منطبق ترجمه میشود.
منشاء شیوه نامه ها، برگه های سبک پیش فرض مرورگر، شیوه نامه های ارائه شده توسط نویسنده صفحه و شیت های سبک کاربر هستند - اینها شیوه نامه هایی هستند که توسط کاربر مرورگر ارائه می شود (مرورگرها به شما امکان می دهند سبک های مورد علاقه خود را تعریف کنید. به عنوان مثال، در فایرفاکس، این با قرار دادن یک style sheet در پوشه "Firefox Profile" انجام می شود).
محاسبه سبک چند مشکل را به همراه دارد:
- داده های سبک یک ساختار بسیار بزرگ است که دارای ویژگی های سبک متعدد است، این می تواند باعث مشکلات حافظه شود.
یافتن قوانین تطبیق برای هر عنصر در صورتی که بهینه نشده باشد می تواند باعث مشکلات عملکرد شود. پیمودن کل فهرست قوانین برای هر عنصر برای یافتن موارد منطبق، کار سنگینی است. انتخابگرها میتوانند ساختار پیچیدهای داشته باشند که میتواند باعث شود فرآیند تطبیق در مسیری به ظاهر امیدوارکننده شروع شود که بیفایده بودن آن ثابت شده است و باید مسیر دیگری را امتحان کرد.
به عنوان مثال - این انتخابگر ترکیبی:
div div div div{ ... }
به این معنی که قوانین در مورد یک
<div>
که از نسل 3 div است اعمال می شود. فرض کنید می خواهید بررسی کنید که آیا این قانون برای عنصر<div>
داده شده اعمال می شود یا خیر. شما مسیر خاصی را برای بررسی به بالای درخت انتخاب می کنید. ممکن است لازم باشد درخت گره را به سمت بالا طی کنید تا متوجه شوید که فقط دو div وجود دارد و این قانون اعمال نمی شود. سپس باید مسیرهای دیگری را در درخت امتحان کنید.اعمال قوانین شامل قوانین آبشاری کاملاً پیچیده ای است که سلسله مراتب قوانین را تعریف می کند.
بیایید ببینیم مرورگرها چگونه با این مشکلات روبرو می شوند:
به اشتراک گذاری داده های سبک
گره های WebKit به اشیاء سبک ارجاع می دهند (RenderStyle). این اشیاء را می توان در برخی شرایط توسط گره ها به اشتراک گذاشت. گره ها خواهر و برادر یا پسر عمو هستند و:
- عناصر باید در یک حالت ماوس باشند (به عنوان مثال، یکی نمی تواند در :hover باشد در حالی که دیگری نیست)
- هیچ یک از عناصر نباید شناسه داشته باشند
- نام تگ ها باید مطابقت داشته باشد
- ویژگی های کلاس باید مطابقت داشته باشند
- مجموعه ویژگی های نقشه برداری شده باید یکسان باشد
- حالت های پیوند باید مطابقت داشته باشند
- حالات تمرکز باید مطابقت داشته باشند
- هیچ یک از عناصر نباید تحت تأثیر انتخابگرهای مشخصه قرار گیرند، جایی که تأثیر به این صورت تعریف می شود که منطبق بر انتخابگر باشد که از یک انتخابگر ویژگی در هر موقعیتی در انتخابگر استفاده می کند.
- نباید هیچ ویژگی سبک درون خطی روی عناصر وجود داشته باشد
- اصلا نباید انتخابگر خواهر و برادری در حال استفاده باشد. WebCore به سادگی یک سوئیچ سراسری را زمانی که انتخابگر خواهر و برادری با آن مواجه میشود پرتاب میکند و اشتراکگذاری سبک را برای کل سند در زمانی که آنها حضور دارند غیرفعال میکند. این شامل انتخابگر + و انتخابگرهایی مانند :first-child و :last-child است.
درخت قانون فایرفاکس
فایرفاکس دارای دو درخت اضافی برای محاسبه سبک آسان تر است: درخت قانون و درخت زمینه سبک. WebKit همچنین دارای اشیاء سبک است اما آنها در درختی مانند درخت زمینه سبک ذخیره نمی شوند، فقط گره DOM به سبک مربوطه خود اشاره می کند.
زمینه های سبک حاوی مقادیر پایانی هستند. مقادیر با اعمال تمام قوانین تطبیق به ترتیب صحیح و انجام دستکاری هایی که آنها را از مقادیر منطقی به مقادیر مشخص تبدیل می کند، محاسبه می شوند. به عنوان مثال، اگر مقدار منطقی درصدی از صفحه باشد، محاسبه شده و به واحدهای مطلق تبدیل می شود. ایده درخت قانون واقعا هوشمندانه است. به اشتراک گذاری این مقادیر بین گره ها برای جلوگیری از محاسبه مجدد آنها را امکان پذیر می کند. این همچنین باعث صرفه جویی در فضا می شود.
همه قوانین مطابق در یک درخت ذخیره می شوند. گره های پایین در یک مسیر دارای اولویت بیشتری هستند. درخت شامل تمام مسیرهای منطبق قوانین یافت شده است. ذخیره قوانین با تنبلی انجام می شود. درخت در ابتدا برای هر گره محاسبه نمی شود، اما هر زمان که یک سبک گره باید محاسبه شود، مسیرهای محاسبه شده به درخت اضافه می شوند.
ایده این است که مسیرهای درخت را به عنوان کلمات در یک فرهنگ لغت ببینیم. بیایید بگوییم که قبلاً این درخت قانون را محاسبه کرده ایم:
فرض کنید باید قوانین را برای عنصر دیگری در درخت محتوا مطابقت دهیم و متوجه شویم که قوانین مطابق (به ترتیب صحیح) BEI هستند. ما قبلاً این مسیر را در درخت داریم زیرا قبلاً مسیر ABEIL را محاسبه کرده ایم. اکنون کار کمتری برای انجام دادن خواهیم داشت.
بیایید ببینیم درخت چگونه کار ما را نجات می دهد.
تقسیم به سازه ها
زمینه های سبک به ساختارها تقسیم می شوند. این ساختارها حاوی اطلاعات سبک برای یک دسته خاص مانند حاشیه یا رنگ هستند. تمام ویژگی های یک ساختار یا ارثی هستند یا غیر ارثی. ویژگیهای ارثی ویژگیهایی هستند که مگر اینکه توسط عنصر تعریف شوند، از والد آن به ارث میرسند. ویژگیهای غیر ارثی (که ویژگیهای «تنظیم مجدد» نامیده میشوند) اگر تعریف نشده باشند، از مقادیر پیشفرض استفاده میکنند.
درخت با ذخیره سازی کل ساختارها (شامل مقادیر پایانی محاسبه شده) در درخت به ما کمک می کند. ایده این است که اگر گره پایینی تعریفی برای یک ساختار ارائه نمیکند، میتوان از یک ساختار ذخیرهشده در یک گره بالایی استفاده کرد.
محاسبه زمینه های سبک با استفاده از درخت قانون
هنگام محاسبه زمینه سبک برای یک عنصر خاص، ابتدا یک مسیر را در درخت قانون محاسبه می کنیم یا از یک مسیر موجود استفاده می کنیم. سپس شروع به اعمال قوانین در مسیر برای پر کردن ساختارها در زمینه سبک جدید خود می کنیم. ما از گره پایین مسیر شروع می کنیم - یکی با بالاترین اولویت (معمولاً خاص ترین انتخابگر) و درخت را تا زمانی که ساختار ما پر شود، طی می کنیم. اگر هیچ مشخصاتی برای ساختار در آن گره قانون وجود نداشته باشد، میتوانیم تا حد زیادی بهینهسازی کنیم - از درخت بالا میرویم تا زمانی که گرهای را پیدا کنیم که آن را کاملاً مشخص کند و به آن اشاره کنیم - این بهترین بهینهسازی است - کل ساختار به اشتراک گذاشته میشود. این باعث صرفه جویی در محاسبه مقادیر نهایی و حافظه می شود.
اگر تعاریف جزئی پیدا کنیم از درخت بالا می رویم تا ساختار پر شود.
اگر هیچ تعریفی برای ساختار خود پیدا نکردیم، در صورتی که ساختار یک نوع "ارثی" باشد، به ساختار والد خود در درخت زمینه اشاره می کنیم. در این مورد ما همچنین موفق به اشتراک گذاری ساختارها شدیم. اگر ساختار بازنشانی باشد، از مقادیر پیشفرض استفاده میشود.
اگر خاص ترین گره مقادیری را اضافه کند، باید محاسبات اضافی برای تبدیل آن به مقادیر واقعی انجام دهیم. سپس نتیجه را در گره درخت ذخیره می کنیم تا بتوان از آن برای کودکان استفاده کرد.
اگر عنصری خواهر یا برادر یا برادری داشته باشد که به همان گره درخت اشاره می کند، می توان کل زمینه سبک را بین آنها به اشتراک گذاشت.
بیایید یک مثال را ببینیم: فرض کنید ما این 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>
1، 2 و 6 هستند. این بدان معنی است که یک مسیر موجود در درخت وجود دارد که عنصر ما می تواند از آن استفاده کند و فقط باید گره دیگری را به آن اضافه کنیم. قانون 6 (گره F در درخت قانون).
ما یک زمینه سبک ایجاد می کنیم و آن را در درخت زمینه قرار می دهیم. زمینه سبک جدید به گره F در درخت قانون اشاره می کند.
اکنون باید ساختارهای سبک را پر کنیم. ما با پر کردن ساختار حاشیه شروع خواهیم کرد. از آنجایی که آخرین گره قانون (F) به ساختار حاشیه اضافه نمیشود، میتوانیم به بالای درخت برویم تا زمانی که یک ساختار حافظه پنهان محاسبهشده در درج گره قبلی را پیدا کنیم و از آن استفاده کنیم. ما آن را در گره B خواهیم یافت، که بالاترین گره ای است که قوانین حاشیه را مشخص می کند.
ما برای ساختار رنگی تعریفی داریم، بنابراین نمی توانیم از ساختار کش استفاده کنیم. از آنجایی که رنگ یک ویژگی دارد، لازم نیست برای پر کردن سایر ویژگیها از درخت بالا برویم. ما مقدار پایانی را محاسبه می کنیم (رشته را به RGB و غیره تبدیل می کنیم) و ساختار محاسبه شده را در این گره ذخیره می کنیم.
کار بر روی عنصر دوم <span>
حتی ساده تر است. قوانین را مطابقت می دهیم و به این نتیجه می رسیم که مانند دهانه قبلی به قانون G اشاره می کند. از آنجایی که ما خواهر و برادرهایی داریم که به یک گره اشاره میکنند، میتوانیم کل زمینه سبک را به اشتراک بگذاریم و فقط به بافت دهانه قبلی اشاره کنیم.
برای ساختارهایی که حاوی قوانینی هستند که از والد به ارث برده شده اند، کش روی درخت زمینه انجام می شود (ویژگی رنگ در واقع به ارث می رسد، اما فایرفاکس آن را به عنوان تنظیم مجدد در نظر می گیرد و آن را در درخت قانون ذخیره می کند).
به عنوان مثال، اگر قوانینی را برای فونت ها در یک پاراگراف اضافه کنیم:
p {font-family: Verdana; font size: 10px; font-weight: bold}
سپس عنصر پاراگراف، که فرزندی از div در درخت زمینه است، میتوانست همان ساختار فونت را با پدرش به اشتراک بگذارد. این در صورتی است که هیچ قانون فونتی برای پاراگراف مشخص نشده باشد.
در WebKit که درخت قانون ندارد، اعلانهای همسان چهار بار پیمایش میشوند. ابتدا ویژگیهای غیر مهم با اولویت بالا اعمال میشوند (خواصی که ابتدا باید اعمال شوند زیرا سایرین به آنها وابسته هستند، مانند نمایش)، سپس اولویت بالا مهم، سپس اولویت عادی غیر مهم، سپس قوانین مهم اولویت عادی. این به این معنی است که ویژگی هایی که چندین بار ظاهر می شوند مطابق ترتیب آبشاری صحیح حل می شوند. آخرین برنده است.
بنابراین به طور خلاصه: اشتراک گذاری اشیاء سبک (به طور کامل یا برخی از ساختارهای داخل آنها) مسائل 1 و 3 را حل می کند. درخت قانون فایرفاکس همچنین به اعمال خواص به ترتیب صحیح کمک می کند.
دستکاری قوانین برای یک مسابقه آسان
چندین منبع برای قوانین سبک وجود دارد:
- قوانین CSS، چه در شیوه نامه های خارجی یا در عناصر سبک.
css p {color: blue}
- ویژگی های سبک درون خطی مانند
html <p style="color: blue" />
- ویژگیهای بصری HTML (که به قوانین سبک مربوطه نگاشت میشوند)
html <p bgcolor="blue" />
دو مورد آخر به راحتی با عنصر تطبیق داده میشوند زیرا او مالک ویژگیهای سبک است و ویژگیهای HTML را میتوان با استفاده از عنصر به عنوان کلید نگاشت کرد.
همانطور که قبلا در شماره 2 ذکر شد، تطبیق قوانین CSS می تواند پیچیده تر باشد. برای حل مشکل، قوانین برای دسترسی آسان تر دستکاری می شوند.
پس از تجزیه صفحه سبک، طبق انتخابگر، قوانین به یکی از چندین نقشه هش اضافه می شوند. نقشه هایی بر اساس شناسه، نام کلاس، بر اساس نام تگ و یک نقشه کلی برای هر چیزی که در آن دسته بندی ها قرار نمی گیرد وجود دارد. اگر انتخابگر یک id باشد، قانون به نقشه شناسه اضافه می شود، اگر کلاس باشد به نقشه کلاس و غیره اضافه می شود.
این دستکاری تطبیق قوانین را بسیار آسان تر می کند. نیازی به جستجو در هر اعلان نیست: ما می توانیم قوانین مربوط به یک عنصر را از نقشه ها استخراج کنیم. این بهینه سازی 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 قوانین مربوطه را در نقشه شناسه (کلید id است) و نقشه برچسب خواهد داشت. بنابراین تنها کار باقی مانده این است که بفهمیم کدام یک از قوانین استخراج شده توسط کلیدها واقعاً مطابقت دارند.
برای مثال اگر قانون div این بود:
table div {margin: 5px}
همچنان از نقشه برچسب استخراج می شود، زیرا کلید سمت راست ترین انتخابگر است، اما با عنصر div ما که جد جدولی ندارد مطابقت ندارد.
هم WebKit و هم Firefox این دستکاری را انجام می دهند.
سفارش آبشار ورق سبک
شی استایل دارای ویژگی های مربوط به هر ویژگی بصری است (همه ویژگی های CSS اما عمومی تر). اگر خاصیت با هیچ یک از قوانین منطبق تعریف نشده باشد، برخی از ویژگی ها را می توان توسط شیء سبک عنصر والد به ارث برد. سایر ویژگی ها دارای مقادیر پیش فرض هستند.
مشکل زمانی شروع می شود که بیش از یک تعریف وجود داشته باشد - در اینجا دستور آبشار برای حل مسئله می آید.
یک اعلان برای یک ویژگی سبک میتواند در چندین شیوه نامه و چندین بار در یک شیوه نامه ظاهر شود. این بدان معناست که ترتیب اعمال قوانین بسیار مهم است. به این ترتیب "آبشار" می گویند. طبق مشخصات CSS2، ترتیب آبشار (از کم به زیاد) است:
- اعلامیه های مرورگر
- اظهارنامه های عادی کاربر
- اظهارنامه های عادی نویسنده
- نویسنده اظهارات مهم
- اعلامیه های مهم کاربر
اعلان های مرورگر کمترین اهمیت را دارند و کاربر تنها در صورتی از نویسنده رد می کند که اعلان به عنوان مهم علامت گذاری شده باشد. اعلانهای با همان ترتیب بر اساس ویژگی مرتبسازی میشوند و سپس به ترتیبی که مشخص میشوند. ویژگیهای بصری HTML به اعلانهای CSS منطبق ترجمه میشوند. آنها به عنوان قوانین نویسنده با اولویت پایین در نظر گرفته می شوند.
خاص بودن
ویژگی انتخابگر توسط مشخصات CSS2 به صورت زیر تعریف می شود:
- اگر اعلانی که از آن گرفته شده است به جای یک قانون با انتخابگر، یک ویژگی «style» است، 0 در غیر این صورت (= a)
- شمارش تعداد ویژگی های ID در انتخابگر (= b)
- تعداد سایر صفات و شبه کلاس ها را در انتخابگر بشمارید (= c)
- تعداد نام عناصر و شبه عناصر را در انتخابگر بشمارید (=d)
الحاق چهار عدد abcd (در یک سیستم اعداد با پایه بزرگ) ویژگی را می دهد.
پایه اعدادی که باید استفاده کنید با بالاترین تعداد شما در یکی از دسته ها تعریف می شود.
به عنوان مثال، اگر 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) بارگیری شدهاند یا خیر. اگر استایل هنگام اتصال به طور کامل بارگذاری نشده باشد، از نگهدارندههای مکان استفاده میشود و در سند مشخص میشود و پس از بارگیری شیوه نامهها مجدداً محاسبه میشوند.
طرح بندی
وقتی رندر ایجاد می شود و به درخت اضافه می شود، موقعیت و اندازه ندارد. محاسبه این مقادیر را layout یا reflow می نامند.
HTML از یک مدل طرحبندی مبتنی بر جریان استفاده میکند، به این معنی که در بیشتر مواقع میتوان هندسه را در یک پاس محاسبه کرد. عناصر بعدی "در جریان" معمولاً بر هندسه عناصری که زودتر "در جریان هستند" تأثیر نمیگذارند، بنابراین طرحبندی میتواند از چپ به راست، از بالا به پایین در سند ادامه یابد. استثنائاتی وجود دارد: برای مثال، جداول HTML ممکن است به بیش از یک پاس نیاز داشته باشند.
سیستم مختصات نسبت به قاب ریشه است. مختصات بالا و چپ استفاده می شود.
Layout یک فرآیند بازگشتی است. از رندر اصلی شروع می شود که با عنصر <html>
سند HTML مطابقت دارد. Layout به صورت بازگشتی از طریق برخی یا تمام سلسله مراتب فریم ادامه می یابد و اطلاعات هندسی را برای هر رندری که به آن نیاز دارد محاسبه می کند.
موقعیت رندر ریشه 0،0 و ابعاد آن نمای درگاه است - قسمت قابل مشاهده پنجره مرورگر.
همه رندرها یک روش "layout" یا "reflow" دارند، هر رندر متد طرح بندی فرزندان خود را که نیاز به چیدمان دارند فراخوانی می کند.
سیستم بیت کثیف
برای اینکه برای هر تغییر کوچک یک طرح بندی کامل انجام نشود، مرورگرها از یک سیستم "کثیف بیت" استفاده می کنند. رندری که تغییر یا اضافه شده است، خود و فرزندانش را به عنوان "کثیف" علامت گذاری می کند: نیاز به طرح بندی.
دو پرچم وجود دارد: "کثیف"، و "کودکان کثیف هستند"، به این معنی که اگرچه خود رندر ممکن است خوب باشد، اما حداقل یک فرزند دارد که به طرح بندی نیاز دارد.
طرح کلی و افزایشی
Layout می تواند در کل درخت رندر فعال شود - این طرح "جهانی" است. این می تواند در نتیجه اتفاق بیفتد:
- یک تغییر سبک جهانی که بر همه رندرها تأثیر می گذارد، مانند تغییر اندازه فونت.
- در نتیجه تغییر اندازه صفحه نمایش
چیدمان می تواند افزایشی باشد، فقط رندرهای کثیف چیده می شوند (این می تواند باعث آسیب هایی شود که به چیدمان های اضافی نیاز دارد).
وقتی رندر کثیف باشد، طرحبندی افزایشی (ناهمزمان) فعال میشود. به عنوان مثال زمانی که رندرهای جدید به درخت رندر اضافه می شوند، پس از اینکه محتوای اضافی از شبکه آمد و به درخت DOM اضافه شد.
چیدمان ناهمزمان و سنکرون
چیدمان افزایشی به صورت ناهمزمان انجام می شود. فایرفاکس «فرمانهای جریان مجدد» را برای طرحبندیهای افزایشی در صف قرار میدهد و یک زمانبندی اجرای دستهای این دستورات را آغاز میکند. WebKit همچنین دارای یک تایمر است که یک طرح بندی افزایشی را اجرا می کند - درخت عبور می کند و رندرهای "کثیف" طرح بندی می شوند.
اسکریپت هایی که اطلاعات سبک را می خواهند، مانند "offsetHeight" می توانند طرح بندی افزایشی را به طور همزمان فعال کنند.
طرح کلی معمولاً به صورت همزمان فعال می شود.
گاهی اوقات طرح به عنوان یک تماس پس از طرح اولیه فعال می شود زیرا برخی از ویژگی ها مانند موقعیت پیمایش تغییر کرده است.
بهینه سازی ها
هنگامی که یک طرح با یک "تغییر اندازه" یا تغییر در موقعیت رندر (و نه اندازه) راه اندازی می شود، اندازه های رندر از حافظه پنهان گرفته می شوند و دوباره محاسبه نمی شوند…
در برخی موارد فقط یک درخت فرعی اصلاح می شود و طرح از ریشه شروع نمی شود. این می تواند در مواردی اتفاق بیفتد که تغییر محلی است و محیط اطراف خود را تحت تأثیر قرار نمی دهد - مانند متن درج شده در فیلدهای متنی (در غیر این صورت هر ضربه کلید باعث ایجاد طرحی می شود که از ریشه شروع می شود).
فرآیند چیدمان
طرح معمولاً دارای الگوی زیر است:
- رندر والد عرض خود را تعیین می کند.
- والدین به سراغ فرزندان می روند و:
- رندر فرزند را قرار دهید (x و y آن را تنظیم می کند).
- در صورت نیاز طرح فرزند را صدا می کند - آنها کثیف هستند یا ما در یک چیدمان جهانی هستیم یا به دلایل دیگر - که قد کودک را محاسبه می کند.
- والدین از ارتفاعهای انباشته کودکان و ارتفاع حاشیهها و بالشتکها برای تنظیم ارتفاع خود استفاده میکنند - این مورد توسط والدین رندر والد استفاده خواهد شد.
- بیت کثیف خود را روی false تنظیم می کند.
فایرفاکس از یک شی "state" (nsHTMLReflowState) به عنوان پارامتری برای چیدمان استفاده می کند (که "reflow" نامیده می شود). در میان سایر موارد، این ایالت شامل عرض والدین نیز می شود.
خروجی طرح فایرفاکس یک شی "متریک" (nsHTMLReflowMetrics) است. این شامل ارتفاع محاسبه شده رندر خواهد بود.
محاسبه عرض
عرض رندر با استفاده از عرض بلوک کانتینر، ویژگی "width" سبک رندر، حاشیه ها و حاشیه ها محاسبه می شود.
به عنوان مثال عرض div زیر:
<div style="width: 30%"/>
توسط WebKit به صورت زیر محاسبه می شود (روش کلاس RenderBox calcWidth):
- عرض کانتینر حداکثر کانتینرهای موجودWidth و 0 است. در این مورد، عرض موجود در این حالت، ContentWidth است که به صورت زیر محاسبه می شود:
clientWidth() - paddingLeft() - paddingRight()
clientWidth و clientHeight نمایانگر فضای داخلی یک شی به استثنای حاشیه و نوار اسکرول است.
عرض عناصر ویژگی سبک "width" است. با محاسبه درصد عرض ظرف به عنوان یک مقدار مطلق محاسبه می شود.
حاشیه های افقی و بالشتک ها اکنون اضافه شده اند.
تا اینجا این محاسبه "عرض ترجیحی" بود. اکنون حداقل و حداکثر عرض محاسبه خواهد شد.
اگر عرض ترجیحی بیشتر از حداکثر عرض باشد، از حداکثر عرض استفاده می شود. اگر کمتر از حداقل عرض (کوچکترین واحد نشکن) باشد از حداقل عرض استفاده می شود.
در صورت نیاز به طرح بندی، مقادیر در حافظه پنهان ذخیره می شوند، اما عرض تغییر نمی کند.
خط شکستن
هنگامی که یک رندر در وسط یک چیدمان تصمیم می گیرد که باید شکسته شود، رندر متوقف می شود و به والد چیدمان می گوید که باید شکسته شود. والد رندرهای اضافی را ایجاد می کند و طرح بندی را روی آنها فراخوانی می کند.
نقاشی
در مرحله نقاشی، درخت رندر پیمایش می شود و متد "paint()" رندر برای نمایش محتوا در صفحه فراخوانی می شود. نقاشی از مؤلفه زیرساخت رابط کاربری استفاده می کند.
جهانی و افزایشی
مانند چیدمان، نقاشی نیز می تواند جهانی باشد - کل درخت رنگ شده است - یا افزایشی. در نقاشی افزایشی، برخی از رندرها به گونه ای تغییر می کنند که بر کل درخت تأثیر نمی گذارد. رندر تغییر یافته مستطیل آن را روی صفحه باطل می کند. این باعث می شود که سیستم عامل آن را به عنوان یک "منطقه کثیف" ببیند و یک رویداد "رنگ" ایجاد کند. سیستم عامل این کار را هوشمندانه انجام می دهد و چندین منطقه را در یک منطقه ادغام می کند. در کروم پیچیدهتر است زیرا رندر در فرآیندی متفاوت از فرآیند اصلی است. کروم تا حدودی رفتار سیستم عامل را شبیه سازی می کند. ارائه به این رویدادها گوش می دهد و پیام را به ریشه رندر می دهد. درخت تا رسیدن به رندر مربوطه طی می شود. خودش (و معمولاً فرزندانش) را دوباره رنگ می کند.
دستور نقاشی
CSS2 ترتیب فرآیند نقاشی را مشخص می کند . این در واقع ترتیبی است که عناصر در زمینه های انباشته روی هم چیده می شوند. این ترتیب روی نقاشی تأثیر می گذارد زیرا پشته ها از پشت به جلو رنگ می شوند. ترتیب انباشته شدن یک رندر بلوک به صورت زیر است:
- رنگ پس زمینه
- تصویر پس زمینه
- مرز
- کودکان
- طرح کلی
لیست نمایش فایرفاکس
فایرفاکس روی درخت رندر می رود و یک لیست نمایشی برای مستطیل رنگ شده می سازد. این شامل رندرهای مربوط به مستطیل، به ترتیب نقاشی درست (پس زمینه رندرها، سپس حاشیه ها و غیره) است.
به این ترتیب درخت باید فقط یک بار برای رنگ آمیزی مجدد به جای چندین بار پیمایش شود - تمام پس زمینه ها، سپس همه تصاویر، سپس تمام حاشیه ها و غیره.
فایرفاکس فرآیند را با اضافه نکردن عناصری که پنهان میشوند، بهینه میکند، مانند عناصری که کاملاً در زیر عناصر غیر شفاف دیگر قرار دارند.
ذخیره سازی مستطیل WebKit
قبل از رنگ آمیزی مجدد، WebKit مستطیل قدیمی را به عنوان یک بیت مپ ذخیره می کند. سپس فقط دلتای بین مستطیل جدید و قدیمی را رنگ می کند.
تغییرات پویا
مرورگرها سعی می کنند حداقل اقدامات ممکن را در پاسخ به یک تغییر انجام دهند. بنابراین تغییر در رنگ یک عنصر فقط باعث رنگ آمیزی مجدد عنصر می شود. تغییر در موقعیت عنصر باعث چیدمان و رنگ آمیزی مجدد عنصر، فرزندان آن و احتمالاً خواهر و برادر می شود. افزودن یک گره DOM باعث طرح بندی و رنگ آمیزی مجدد گره می شود. تغییرات عمده، مانند افزایش اندازه قلم عنصر "html"، باعث بی اعتباری حافظه پنهان، تغییر شکل و رنگ آمیزی مجدد کل درخت می شود.
نخ های موتور رندر
موتور رندر تک رشته است. تقریباً همه چیز، به جز عملیات شبکه، در یک رشته اتفاق می افتد. در فایرفاکس و سافاری این موضوع اصلی مرورگر است. در کروم، موضوع اصلی پردازش برگه است.
عملیات شبکه می تواند توسط چندین رشته موازی انجام شود. تعداد اتصالات موازی محدود است (معمولاً 2 - 6 اتصال).
حلقه رویداد
موضوع اصلی مرورگر یک حلقه رویداد است. این یک حلقه بی نهایت است که روند را زنده نگه می دارد. منتظر رویدادها (مانند رویدادهای چیدمان و نقاشی) می ماند و آنها را پردازش می کند. این کد فایرفاکس برای حلقه رویداد اصلی است:
while (!mExiting)
NS_ProcessNextEvent(thread);
مدل بصری CSS2
بوم
بر اساس مشخصات CSS2 ، اصطلاح بوم «فضایی که ساختار قالببندی ارائه میشود» را توصیف میکند: جایی که مرورگر محتوا را نقاشی میکند.
بوم برای هر بعد از فضا بی نهایت است اما مرورگرها یک عرض اولیه را بر اساس ابعاد درگاه دید انتخاب می کنند.
با توجه به www.w3.org/TR/CSS2/zindex.html ، بوم اگر داخل بوم دیگری باشد شفاف است و در صورتی که این بوم به آن رنگ تعریف شده از مرورگر داده شود.
مدل جعبه CSS
مدل جعبه CSS جعبههای مستطیلی را که برای عناصر در درخت سند ایجاد میشوند و مطابق با مدل قالببندی بصری چیده میشوند، توصیف میکند.
هر جعبه دارای یک ناحیه محتوا (مثلاً متن، یک تصویر، و غیره) و قسمت های اضافی، حاشیه و حاشیه است.
هر گره 0…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 قرار می گیرد
طرح موقعیت یابی توسط ویژگی "position" و ویژگی "float" تنظیم می شود.
- استاتیک و نسبی باعث یک جریان طبیعی می شود
- مطلق و ثابت باعث موقعیت یابی مطلق می شود
در موقعیت یابی استاتیک هیچ موقعیتی تعریف نمی شود و از موقعیت یابی پیش فرض استفاده می شود. در طرح های دیگر، نویسنده موقعیت را مشخص می کند: بالا، پایین، چپ، راست.
نحوه چیدمان جعبه با موارد زیر تعیین می شود:
- نوع جعبه
- ابعاد جعبه
- طرح موقعیت یابی
- اطلاعات خارجی مانند اندازه تصویر و اندازه صفحه نمایش
انواع جعبه
جعبه بلوک: یک بلوک را تشکیل می دهد - مستطیل خود را در پنجره مرورگر دارد.
جعبه درون خطی: بلوک خود را ندارد، اما درون یک بلوک حاوی است.
بلوک ها به صورت عمودی یکی پس از دیگری قالب بندی می شوند. خطوط درونی به صورت افقی قالب بندی می شوند.
جعبه های درون خطی در داخل خطوط یا "جعبه های خط" قرار می گیرند. خطوط حداقل به بلندی بلندترین جعبه هستند، اما می توانند بلندتر باشند، زمانی که کادرها "خط پایه" تراز شوند - به این معنی که قسمت پایینی یک عنصر در نقطه ای از کادر دیگری غیر از پایین تراز شده است. اگر عرض ظرف کافی نباشد، خطوط درونی روی چندین خط قرار می گیرند. معمولاً در یک پاراگراف این اتفاق می افتد.
موقعیت یابی
نسبی
موقعیت یابی نسبی - مانند معمول قرار گرفته و سپس توسط دلتای مورد نیاز جابجا می شود.
شناورها
یک جعبه شناور به سمت چپ یا راست یک خط منتقل می شود. ویژگی جالب این است که جعبه های دیگر در اطراف آن جریان دارند. HTML:
<p>
<img style="float: right" src="images/image.gif" width="100" height="100">
Lorem ipsum dolor sit amet, consectetuer...
</p>
به نظر خواهد رسید:
مطلق و ثابت
طرح دقیقاً بدون توجه به جریان عادی تعریف می شود. عنصر در جریان عادی شرکت نمی کند. ابعاد نسبت به ظرف است. در حالت ثابت، کانتینر درگاه دید است.
نمایندگی لایه ای
این توسط ویژگی 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>
نتیجه این خواهد بود:
اگر چه div قرمز قبل از رنگ سبز در نشانه گذاری است، و قبلاً در جریان معمولی رنگ می شد، ویژگی z-index بالاتر است، بنابراین در پشته ای که توسط جعبه ریشه نگهداری می شود جلوتر است.
منابع
معماری مرورگر
- گروسکورت، آلن. معماری مرجع برای مرورگرهای وب (pdf)
- گوپتا، وینیت. نحوه کار مرورگرها - قسمت 1 - معماری
تجزیه
- آهو، ستی، اولمان، کامپایلرها: اصول، تکنیکها و ابزارها (با نام مستعار کتاب اژدها)، ادیسون-وسلی، 1986
- ریک جلیف The Bold and the Beautiful: دو پیش نویس جدید برای HTML 5.
فایرفاکس
- L. David Baron، سریعتر HTML و CSS: Layout Engine Internals for Web Developers.
- L. David Baron، HTML سریعتر و CSS: Layout Engine Internals for Web Developers (فیلم گفتگوی فناوری گوگل)
- ال. دیوید بارون، موتور چیدمان موزیلا
- L. David Baron، مستندات سیستم سبک موزیلا
- کریس واترسون، یادداشت هایی در مورد HTML Reflow
- کریس واترسون، بررسی اجمالی مارمولک
- الکساندر لارسون، زندگی یک درخواست HTTP HTML
وب کیت
- دیوید حیات، پیاده سازی CSS (قسمت 1)
- دیوید حیات، مروری بر WebCore
- دیوید حیات، WebCore Rendering
- دیوید حیات، مسئله FOUC
مشخصات W3C
مرورگرها دستورالعمل های ساخت
ترجمه ها
این صفحه دو بار به ژاپنی ترجمه شده است:
- چگونه مرورگرها کار می کنند - پشت صحنه مرورگرهای وب مدرن (ja) توسط @ kosei
- ブラウザってどうやって動いてるの?(モダンWEBブラウザシーンの裏 40 توسط @ikeki 40 و @ikeki.40
می توانید ترجمه های کره ای و ترکی میزبان خارجی را مشاهده کنید.
با تشکر از همه!