เบื้องหลังเว็บเบราว์เซอร์สมัยใหม่
บทนำ
ข้อมูลเบื้องต้นที่ครอบคลุมเกี่ยวกับการดำเนินการภายในของ WebKit และ Gecko คือ ผลการวิจัยจำนวนมากของ Tali Garsiel ซึ่งเป็นนักพัฒนาชาวอิสราเอล มากกว่า 2-3 เธอได้ตรวจสอบข้อมูลที่เผยแพร่ทั้งหมดเกี่ยวกับข้อมูลภายในของเบราว์เซอร์และใช้เวลา บ่อยครั้งที่อ่านซอร์สโค้ดของเว็บเบราว์เซอร์ เธอเขียนว่า
ในฐานะนักพัฒนาเว็บ การเรียนรู้ภายในของการดำเนินการของเบราว์เซอร์ ช่วยให้คุณตัดสินใจได้ดีขึ้นและรู้เหตุผลเบื้องหลังการพัฒนา แนวทางปฏิบัติแนะนำ แม้ว่าเอกสารนี้จะค่อนข้างยาว เราขอแนะนำ ให้คุณใช้เวลากับการค้นคว้าอย่างละเอียด แล้วคุณจะชอบ
Paul Ireland ฝ่ายนักพัฒนาซอฟต์แวร์ Chrome สัมพันธ์
บทนำ
เว็บเบราว์เซอร์เป็นซอฟต์แวร์ที่มีการใช้กันอย่างแพร่หลายที่สุด ในเกริ่นนำนี้ ฉันจะอธิบายถึง
พวกเขาทำงานอยู่เบื้องหลัง เราจะเห็นสิ่งที่เกิดขึ้นเมื่อคุณพิมพ์ google.com
ในแถบที่อยู่จนกระทั่งเห็นหน้า Google บนหน้าจอเบราว์เซอร์
เบราว์เซอร์ที่เราจะพูดถึง
เบราว์เซอร์หลักที่ใช้บนเดสก์ท็อปในปัจจุบันมี 5 ชนิด ได้แก่ Chrome, Internet Explorer, Firefox, Safari และ Opera สำหรับบนมือถือ เบราว์เซอร์หลัก ได้แก่ เบราว์เซอร์ Android, iPhone, Opera Mini และ Opera Mobile, เบราว์เซอร์ UC, เบราว์เซอร์ Nokia S40/S60 และ Chrome ซึ่งทั้งหมดนั้นใช้ WebKit เป็นเบราว์เซอร์ ยกเว้นเบราว์เซอร์ Opera ฉันจะยกตัวอย่างจากเบราว์เซอร์โอเพนซอร์ส Firefox และ Chrome และ Safari (ซึ่งบางส่วนเป็นโอเพนซอร์ส) จากข้อมูลของ StatCounterStatistics (ข้อมูล ณ เดือนมิถุนายน 2013) Chrome พบว่า Firefox และ Safari คิดเป็นประมาณ 71% ของการใช้งานเบราว์เซอร์บนเดสก์ท็อปทั่วโลก บนอุปกรณ์เคลื่อนที่ เบราว์เซอร์ Android, iPhone และ Chrome คิดเป็นประมาณ 54% ของการใช้งานทั้งหมด
ฟังก์ชันหลักของเบราว์เซอร์
ฟังก์ชันหลักของเบราว์เซอร์คือการนำเสนอแหล่งข้อมูลบนเว็บที่คุณเลือก ด้วยการขอข้อมูลจากเซิร์ฟเวอร์และแสดงทรัพยากรนั้นในหน้าต่างเบราว์เซอร์ ทรัพยากรมักจะเป็นเอกสาร HTML แต่ก็อาจเป็น PDF, รูปภาพ หรือเนื้อหาประเภทอื่นๆ ได้ ผู้ใช้จะระบุตำแหน่งของทรัพยากรโดยใช้ URI (Uniform Resource Identifier)
วิธีที่เบราว์เซอร์ตีความและแสดงไฟล์ HTML จะระบุไว้ในข้อกำหนด HTML และ CSS ข้อกำหนดเหล่านี้ได้รับการดูแลโดยองค์กร W3C (World Wide Web Consortium) ซึ่งเป็นองค์กรมาตรฐานสำหรับเว็บ เป็นเวลาหลายปีเลยที่เบราว์เซอร์ยังคงเป็นไปตามข้อกำหนดเฉพาะเพียงบางส่วนและได้พัฒนาส่วนขยายของตัวเองขึ้นมา ซึ่งก่อให้เกิดปัญหาความเข้ากันได้ที่ร้ายแรงสำหรับผู้เขียนเว็บ ปัจจุบัน เบราว์เซอร์ส่วนใหญ่สอดคล้องกับข้อกำหนดเฉพาะต่างๆ มากขึ้นหรือน้อยลง
อินเทอร์เฟซผู้ใช้ของเบราว์เซอร์มีอะไรหลายอย่างเหมือนกัน องค์ประกอบโดยทั่วไปของอินเทอร์เฟซผู้ใช้มีดังนี้
- แถบที่อยู่สำหรับการแทรก URI
- ปุ่มย้อนกลับและไปข้างหน้า
- ตัวเลือกการบุ๊กมาร์ก
- ปุ่มรีเฟรชและหยุดเพื่อรีเฟรชหรือหยุดการโหลดเอกสารปัจจุบัน
- ปุ่มหน้าแรกที่จะนำคุณไปยังหน้าแรก
ที่แปลกก็คือ อินเทอร์เฟซผู้ใช้ของเบราว์เซอร์ไม่ได้ระบุไว้ในข้อกำหนดอย่างเป็นทางการใดๆ เพียงแค่มาจากแนวทางปฏิบัติที่ดีที่มีประสบการณ์หลายปีและโดยการเบราว์เซอร์ลอกเลียนแบบซึ่งกันและกัน ข้อกำหนด HTML5 ไม่ได้กำหนดองค์ประกอบ UI ที่เบราว์เซอร์ต้องมี แต่แสดงรายการองค์ประกอบทั่วไปบางอย่าง หนึ่งในนั้นคือแถบที่อยู่ แถบสถานะ และแถบเครื่องมือ ซึ่งมีคุณลักษณะเฉพาะของเบราว์เซอร์บางประเภท เช่น Download Manager ของ Firefox
โครงสร้างพื้นฐานระดับสูง
องค์ประกอบหลักของเบราว์เซอร์ได้แก่
- อินเทอร์เฟซผู้ใช้ ได้แก่ แถบที่อยู่ ปุ่มย้อนกลับ เมนูการบุ๊กมาร์ก ฯลฯ ทุกส่วนของเบราว์เซอร์จะปรากฏขึ้น ยกเว้นหน้าต่างที่คุณเห็นหน้าที่ขอ
- เครื่องมือเบราว์เซอร์: การทำงานแบบมาร์ชัลระหว่าง UI และเครื่องมือการแสดงผล
- เครื่องมือการแสดงผล: ทำหน้าที่แสดงเนื้อหาที่ขอ ตัวอย่างเช่น หากเนื้อหาที่ขอคือ HTML เครื่องมือการแสดงผลจะแยกวิเคราะห์ HTML และ CSS และแสดงเนื้อหาที่ถูกแยกวิเคราะห์บนหน้าจอ
- เครือข่าย: สำหรับการเรียกเครือข่าย เช่น คำขอ HTTP โดยใช้การติดตั้งใช้งานที่แตกต่างกันสำหรับแพลตฟอร์มต่างๆ เบื้องหลังอินเทอร์เฟซที่ไม่ขึ้นอยู่กับแพลตฟอร์ม
- แบ็กเอนด์ UI: ใช้ในการวาดวิดเจ็ตพื้นฐาน เช่น ช่องตัวเลือกรวมและหน้าต่าง แบ็กเอนด์นี้จะแสดงอินเทอร์เฟซทั่วไปที่ไม่เจาะจงแพลตฟอร์ม ส่วนด้านล่างจะใช้เมธอดอินเทอร์เฟซผู้ใช้ของระบบปฏิบัติการ
- อินเทอร์พรีเตอร์ของ JavaScript ใช้เพื่อแยกวิเคราะห์และเรียกใช้โค้ด JavaScript
- พื้นที่เก็บข้อมูล นี่คือเลเยอร์ถาวร เบราว์เซอร์อาจต้องบันทึกข้อมูลทุกประเภทไว้ในเครื่อง เช่น คุกกี้ เบราว์เซอร์ยังรองรับกลไกพื้นที่เก็บข้อมูล เช่น localStorage, IndexedDB, WebSQL และ FileSystem
![คอมโพเนนต์ของเบราว์เซอร์](https://web.dev/static/articles/howbrowserswork/image/browser-components-9cd8ff834cc9c.png?authuser=6&hl=th)
โปรดทราบว่าเบราว์เซอร์อย่างเช่น Chrome จะเรียกใช้เครื่องมือแสดงผลหลายอินสแตนซ์ นั่นคือ 1 รายการต่อแท็บ แต่ละแท็บจะทำงานในกระบวนการที่แยกจากกัน
เครื่องมือแสดงผล
หน้าที่ของเครื่องมือเรนเดอร์ก็คือ... การแสดงผล ซึ่งก็คือการแสดงเนื้อหาที่ขอบนหน้าจอเบราว์เซอร์
โดยค่าเริ่มต้น เครื่องมือการแสดงผลจะแสดงเอกสารและรูปภาพ HTML และ XML ได้ สามารถแสดงข้อมูลประเภทอื่นๆ ผ่านปลั๊กอินหรือส่วนขยายได้ ตัวอย่างเช่น การแสดงเอกสาร PDF โดยใช้ปลั๊กอินโปรแกรมอ่าน PDF อย่างไรก็ตาม ในบทนี้เราจะเน้นที่กรณีการใช้งานหลัก นั่นคือการแสดง HTML และรูปภาพที่จัดรูปแบบโดยใช้ CSS
เบราว์เซอร์ต่างๆ ใช้เครื่องมือการแสดงผลที่แตกต่างกัน: Internet Explorer ใช้ Trident, Firefox ใช้ Gecko, Safari ใช้ WebKit Chrome และ Opera (จากเวอร์ชัน 15) ใช้ Blink ซึ่งเป็นส้อมของ WebKit
WebKit เป็นเครื่องมือการแสดงผลแบบโอเพนซอร์สที่เริ่มต้นจากการเป็นกลไกสำหรับแพลตฟอร์ม Linux และได้รับการแก้ไขโดย Apple ให้รองรับ Mac และ Windows
ขั้นตอนหลัก
เครื่องมือการแสดงผลจะเริ่มรับเนื้อหาของเอกสารที่ขอ จากเลเยอร์เครือข่าย โดยปกติจะทำเป็นกลุ่มขนาด 8 KB
หลังจากนั้นเป็นขั้นตอนพื้นฐานของเครื่องมือการแสดงผล
![ขั้นตอนพื้นฐานของการแสดงภาพเครื่องมือ](https://web.dev/static/articles/howbrowserswork/image/rendering-engine-basic-fl-2fba02b24e871.png?authuser=6&hl=th)
เครื่องมือการแสดงผลจะเริ่มแยกวิเคราะห์เอกสาร HTML และแปลงองค์ประกอบเป็นโหนด DOM ในโครงสร้างที่เรียกว่า "โครงสร้างเนื้อหา" เครื่องมือจะแยกวิเคราะห์ข้อมูลรูปแบบ ทั้งในไฟล์ CSS ภายนอกและในองค์ประกอบของรูปแบบ จะมีการใช้การจัดรูปแบบข้อมูลร่วมกับคำสั่งที่เป็นภาพใน HTML เพื่อสร้างโครงสร้างอีกแบบหนึ่ง ซึ่งก็คือแผนผังการแสดงผล
แผนผังการแสดงภาพมีสี่เหลี่ยมผืนผ้าที่มีคุณลักษณะด้านภาพ เช่น สีและขนาด สี่เหลี่ยมผืนผ้าอยู่ในลำดับที่ถูกต้องที่จะแสดงบนหน้าจอ
หลังจากสร้างแผนผัง Render จะเป็นการสร้าง "layout" ขึ้นมา ขั้นตอนได้ ซึ่งหมายถึงการให้พิกัดที่แต่ละโหนดปรากฏบนหน้าจอ ขั้นตอนถัดไปคือการวาดภาพ โดยระบบจะข้ามผ่านแผนผังแสดงผล และจะระบายสีแต่ละโหนดโดยใช้เลเยอร์แบ็กเอนด์ของ UI
คุณควรเข้าใจว่านี่เป็นกระบวนการที่ค่อยเป็นค่อยไป เพื่อประสบการณ์ของผู้ใช้ที่ดียิ่งขึ้น เครื่องมือแสดงผลจะพยายามแสดงเนื้อหาบนหน้าจอโดยเร็วที่สุด โมเดลจะไม่รอจนกว่าจะมีการแยกวิเคราะห์ HTML ทั้งหมดก่อนที่จะเริ่มสร้างและจัดวางผังการแสดงผล เนื้อหาบางส่วนจะได้รับการแยกวิเคราะห์และแสดง ในขณะที่กระบวนการจะดำเนินต่อไปพร้อมกับเนื้อหาที่เหลือที่มาจากเครือข่าย
ตัวอย่างโฟลว์หลัก
![ขั้นตอนหลักของ WebKit](https://web.dev/static/articles/howbrowserswork/image/webkit-main-flow-b779d50c0cf28.png?authuser=6&hl=th)
![ขั้นตอนหลักของเครื่องมือแสดงภาพ Gecko ของ Mozilla](https://web.dev/static/articles/howbrowserswork/image/mozillas-gecko-rendering-b18e445544965.jpg?authuser=6&hl=th)
จากรูปที่ 3 และ 4 คุณจะเห็นได้ว่าแม้ WebKit และ Gecko จะใช้คำศัพท์ที่แตกต่างกันเล็กน้อย แต่โดยทั่วไปการใช้งานจะเหมือนกัน
ตุ๊กแกเรียกต้นไม้ขององค์ประกอบที่มีการจัดรูปแบบเป็นภาพว่า "แผนผังเฟรม" แต่ละองค์ประกอบคือเฟรม WebKit ใช้คำว่า "Render Tree" และประกอบไปด้วย "Render Objects" WebKit ใช้คำว่า "การออกแบบ" สำหรับการวางองค์ประกอบ ในขณะที่ Gecko เรียกว่า "Reflow" "ไฟล์แนบ" เป็นคำศัพท์ของ WebKit สำหรับการเชื่อมต่อโหนด DOM และข้อมูลภาพเพื่อสร้างต้นไม้แสดงผล ความแตกต่างเล็กน้อยที่ไม่มีความหมายคือ Gecko มีชั้นพิเศษอยู่ระหว่าง HTML และต้นไม้ DOM ซึ่งเรียกว่า "ซิงก์เนื้อหา" และเป็นโรงงานสำหรับสร้างองค์ประกอบ DOM เราจะพูดถึงแต่ละส่วนของขั้นตอนนี้:
การแยกวิเคราะห์ - ทั่วไป
เนื่องจากการแยกวิเคราะห์เป็นกระบวนการที่สำคัญมากในเครื่องมือการแสดงผล เราจะเจาะลึกมากขึ้นเล็กน้อย เรามาเริ่มต้นกันด้วยข้อมูลเบื้องต้นเกี่ยวกับการแยกวิเคราะห์
การแยกวิเคราะห์เอกสารหมายถึงการแปลเอกสารเป็นโครงสร้างที่โค้ดสามารถใช้ได้ ผลของการแยกวิเคราะห์มักจะเป็นแผนผังของโหนดต่างๆ ที่แสดงถึงโครงสร้างของเอกสาร วิธีนี้เรียกว่าแผนผังแยกวิเคราะห์หรือโครงสร้างไวยากรณ์
ตัวอย่างเช่น การแยกวิเคราะห์นิพจน์ 2 + 3 - 1
อาจแสดงผลโครงสร้างต้นไม้นี้
![โหนดต้นไม้ของนิพจน์ทางคณิตศาสตร์](https://web.dev/static/articles/howbrowserswork/image/mathematical-expression-t-6681a2511ead2.png?authuser=6&hl=th)
ไวยากรณ์
การแยกวิเคราะห์จะขึ้นอยู่กับกฎไวยากรณ์ที่เอกสารปฏิบัติตาม ซึ่งก็คือภาษาหรือรูปแบบที่ใช้ในการเขียนนั้นๆ ทุกรูปแบบที่แยกวิเคราะห์ได้ต้องมีไวยากรณ์เชิงกำหนด ซึ่งประกอบด้วยคำศัพท์และกฎไวยากรณ์ โปรแกรมนี้เรียกว่า ไวยากรณ์ที่ไม่มีบริบท ภาษามนุษย์ไม่ใช่ภาษาดังกล่าว จึงไม่สามารถแยกวิเคราะห์ด้วยเทคนิคการแยกวิเคราะห์ปกติ
โปรแกรมแยกวิเคราะห์ - ชุดค่าผสม Lexer
การแยกวิเคราะห์สามารถแบ่งออกเป็นกระบวนการย่อย 2 กระบวนการ ได้แก่ การวิเคราะห์คำศัพท์และการวิเคราะห์ไวยากรณ์
การวิเคราะห์ทางคณิตศาสตร์คือกระบวนการแบ่งอินพุตออกเป็นโทเค็น โทเค็นคือคำศัพท์ทางภาษา ซึ่งเป็นชุดองค์ประกอบที่ใช้สร้างสรรค์ที่ถูกต้อง ในภาษามนุษย์ คำนี้จะประกอบด้วยคำทั้งหมดที่ปรากฏในพจนานุกรมสำหรับภาษานั้น
การวิเคราะห์ไวยากรณ์คือการใช้กฎไวยากรณ์ของภาษา
โปรแกรมแยกวิเคราะห์มักจะแบ่งงานระหว่างองค์ประกอบ 2 ส่วน ได้แก่ lexer (บางครั้งเรียกว่า Tokenizer) ซึ่งมีหน้าที่แยกอินพุตเป็นโทเค็นที่ถูกต้อง และโปรแกรมแยกวิเคราะห์ที่มีหน้าที่สร้างแผนผังการแยกวิเคราะห์โดยการวิเคราะห์โครงสร้างเอกสารตามกฎไวยากรณ์ของภาษา
นักเรียนจะรู้วิธีตัดอักขระที่ไม่เกี่ยวข้องออก เช่น การเว้นวรรคและการขึ้นบรรทัดใหม่
![จากเอกสารต้นฉบับไปจนถึงแยกวิเคราะห์ต้นไม้](https://web.dev/static/articles/howbrowserswork/image/from-source-document-par-c9c8c59da1ef2.png?authuser=6&hl=th)
กระบวนการแยกวิเคราะห์เป็นกระบวนการซ้ำๆ โปรแกรมแยกวิเคราะห์จะขอโทเค็นใหม่กับ lexer แล้วพยายามจับคู่โทเค็นกับกฎไวยากรณ์รายการใดรายการหนึ่ง หากกฎตรงกัน ระบบจะเพิ่มโหนดที่เกี่ยวข้องกับโทเค็นดังกล่าวลงในแผนผังแยกวิเคราะห์และโปรแกรมแยกวิเคราะห์จะขอโทเค็นอีกรายการหนึ่ง
หากไม่มีกฎที่ตรงกัน โปรแกรมแยกวิเคราะห์จะจัดเก็บโทเค็นไว้ภายใน และจะขอโทเค็นต่อไปจนกว่าจะพบกฎที่ตรงกับโทเค็นที่จัดเก็บไว้ภายในทั้งหมด หากไม่พบกฎ โปรแกรมแยกวิเคราะห์จะขอข้อยกเว้น ซึ่งหมายความว่าเอกสารไม่ถูกต้องและมีข้อผิดพลาดทางไวยากรณ์
การแปล
ในหลายๆ กรณี ต้นไม้แยกวิเคราะห์จะไม่ใช่ผลผลิตขั้นสุดท้าย การแยกวิเคราะห์มักจะใช้ในการแปลภาษา ซึ่งก็คือการเปลี่ยนรูปแบบเอกสารอินพุตเป็นอีกรูปแบบหนึ่ง ตัวอย่างคือคอมไพล์ คอมไพเลอร์ที่คอมไพเลอร์ซอร์สโค้ดลงในโค้ดเครื่องจะแยกวิเคราะห์ลงในโครงสร้างการแยกวิเคราะห์ จากนั้นจะแปลโครงสร้างเป็นเอกสารโค้ดของเครื่อง
![ขั้นตอนการคอมไพล์](https://web.dev/static/articles/howbrowserswork/image/compilation-flow-57cfc3aa68a53.png?authuser=6&hl=th)
ตัวอย่างการแยกวิเคราะห์
ในรูปที่ 5 เราสร้างต้นไม้แยกวิเคราะห์จากนิพจน์ทางคณิตศาสตร์ เรามาลองกำหนดภาษาคณิตศาสตร์ง่ายๆ และดูกระบวนการแยกวิเคราะห์กัน
ไวยากรณ์:
- องค์ประกอบที่ใช้สร้างสรรค์ไวยากรณ์ภาษาคือนิพจน์ คำศัพท์ และการดำเนินการ
- ภาษาของเราสามารถมีนิพจน์จำนวนเท่าใดก็ได้
- นิพจน์หมายถึง "คำศัพท์" ตามด้วย "การดำเนินการ" ตามด้วยคำอื่น
- การดำเนินการเป็นโทเค็นบวกหรือโทเค็นลบ
- คำศัพท์คือโทเค็นจำนวนเต็มหรือนิพจน์
มาวิเคราะห์อินพุต 2 + 3 - 1
กัน
สตริงย่อยแรกที่ตรงกับกฎคือ 2
: ตามกฎ #5 ค่านี้เป็นคำศัพท์
การจับคู่ครั้งที่ 2 คือ 2 + 3
: กฎนี้ตรงกับกฎที่ 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 ได้ทั้งหมด ดูคำจำกัดความอย่างเป็นทางการ บทความของ Wikipedia เกี่ยวกับไวยากรณ์ที่ไม่มีบริบท
ประเภทของโปรแกรมแยกวิเคราะห์
โปรแกรมแยกวิเคราะห์มี 2 ประเภท ได้แก่ โปรแกรมแยกวิเคราะห์จากบนลงล่างและจากล่างขึ้นบน คำอธิบายที่เข้าใจง่ายคือโปรแกรมแยกวิเคราะห์จากด้านบนลงล่างจะตรวจสอบโครงสร้างระดับสูงของไวยากรณ์และพยายามหากฎที่ตรงกัน โปรแกรมแยกวิเคราะห์ล่างขึ้นบนจะเริ่มด้วยอินพุตและค่อยๆ เปลี่ยนรูปแบบเป็นกฎไวยากรณ์ โดยเริ่มจากกฎระดับล่างไปจนถึงกฎระดับสูง
มาดูกันว่าโปรแกรมแยกวิเคราะห์ทั้งสองประเภทนี้จะแยกวิเคราะห์ตัวอย่างของเราอย่างไร
โปรแกรมแยกวิเคราะห์จากด้านบนลงล่างจะเริ่มจากกฎในระดับที่สูงกว่า ซึ่งจะระบุ 2 + 3
เป็นนิพจน์ จากนั้นโมเดลจะระบุ 2 + 3 - 1
เป็นนิพจน์ (กระบวนการระบุนิพจน์เปลี่ยนแปลงไป ตรงกับกฎอื่นๆ แต่จุดเริ่มต้นคือกฎระดับสูงสุด)
โปรแกรมแยกวิเคราะห์ด้านล่างขึ้นจะสแกนอินพุตจนกว่าจะจับคู่กฎ จากนั้นแทนที่อินพุตที่ตรงกันด้วยกฎ ซึ่งจะเป็นเช่นนี้ต่อไปจนกว่าจะสิ้นสุดอินพุต นิพจน์ที่ถูกจับคู่บางส่วนจะวางอยู่บนสแต็กของโปรแกรมแยกวิเคราะห์
โปรแกรมแยกวิเคราะห์จากล่างขึ้นบนประเภทนี้เรียกว่าโปรแกรมแยกวิเคราะห์แบบ Shift-Reduce เนื่องจากอินพุตจะเลื่อนไปทางขวา (ลองจินตนาการว่าตัวชี้ชี้ไปที่จุดเริ่มต้นและเลื่อนไปทางขวา) และค่อยๆ ลดเป็นกฎไวยากรณ์
กำลังสร้างโปรแกรมแยกวิเคราะห์โดยอัตโนมัติ
โดยมีเครื่องมือต่างๆ ที่สามารถสร้างโปรแกรมแยกวิเคราะห์ได้ ใส่ไวยากรณ์ในภาษาของคุณลงไป ซึ่งก็คือคำศัพท์และกฎไวยากรณ์ แล้วโค้ดก็จะสร้างโปรแกรมแยกวิเคราะห์ที่ใช้งานได้ขึ้นมา การสร้างโปรแกรมแยกวิเคราะห์ต้องอาศัยความเข้าใจในการแยกวิเคราะห์อย่างลึกซึ้ง และการสร้างโปรแกรมแยกวิเคราะห์ที่มีการเพิ่มประสิทธิภาพด้วยตนเองก็ไม่ใช่เรื่องง่าย โปรแกรมแยกวิเคราะห์นี้จึงมีประโยชน์มาก
WebKit ใช้โปรแกรมแยกวิเคราะห์ที่เป็นที่รู้จัก 2 โปรแกรม ได้แก่ Flex ในการสร้าง lexer และ Bison เพื่อสร้างโปรแกรมแยกวิเคราะห์ (คุณอาจพบเจอโปรแกรมแยกวิเคราะห์ชื่อ Lex และ Yacc) อินพุต Flex คือไฟล์ที่มีการกำหนดนิพจน์ทั่วไปของโทเค็น อินพุตของ Bison คือกฎไวยากรณ์ภาษาในรูปแบบ BNF
โปรแกรมแยกวิเคราะห์ HTML
งานของโปรแกรมแยกวิเคราะห์ HTML คือแยกวิเคราะห์มาร์กอัป HTML เป็นโครงสร้างการแยกวิเคราะห์
ไวยากรณ์ HTML
คำศัพท์และไวยากรณ์ของ HTML ได้รับการกำหนดไว้ในข้อกำหนดเฉพาะที่องค์กร W3C สร้างขึ้น
จากที่เราได้เห็นในบทนำการแยกวิเคราะห์ ไวยากรณ์ไวยากรณ์สามารถกำหนดอย่างเป็นทางการได้โดยใช้รูปแบบอย่างเช่น BNF
ขออภัย หัวข้อโปรแกรมแยกวิเคราะห์ทั่วไปทั้งหมดใช้ไม่ได้กับ HTML (ฉันไม่ได้นำมาใช้เพื่อความสนุก แต่จะใช้ในการแยกวิเคราะห์ CSS และ JavaScript) ไม่สามารถกำหนด HTML ด้วยไวยากรณ์ที่ไม่มีบริบทซึ่งโปรแกรมแยกวิเคราะห์ต้องการได้โดยง่าย
มีการกำหนด HTML - DTD (Document Type Definition) อย่างเป็นทางการ แต่ไม่ใช่ไวยากรณ์อิสระในบริบท
ลักษณะนี้ดูแปลกประหลาดตั้งแต่แรกเห็น HTML ค่อนข้างใกล้เคียงกับ XML มีโปรแกรมแยกวิเคราะห์ XML จำนวนมาก มีรูปแบบ XML ของ HTML - XHTML แล้ว ความแตกต่างสำคัญคืออะไร
สิ่งที่แตกต่างคือวิธี HTML นั้น "ให้อภัย" มากกว่า กล่าวคือทำให้คุณไม่ต้องใส่แท็กบางแท็ก (ซึ่งจะเพิ่มเข้ามาโดยปริยาย) หรือบางครั้งจะข้ามแท็กเริ่มต้นหรือแท็กปิดท้ายก็ได้ เป็นต้น โดยภาพรวมจะเป็น "เบา" ไวยากรณ์ ซึ่งตรงข้ามกับไวยากรณ์ที่แข็งและซับซ้อนของ XML
รายละเอียดที่ดูเหมือนเป็นเรื่องเล็กๆ นี้สร้างความแตกต่างให้กับโลกใบนี้ ในแง่หนึ่ง ข้อความนี้เป็นเหตุผลหลักที่ทำให้ HTML ได้รับความนิยมอย่างมาก โดยให้อภัยความผิดพลาดของคุณและช่วยให้ผู้เขียนเว็บดำเนินการได้ง่ายขึ้น ในทางกลับกัน การเขียนไวยากรณ์ที่เป็นทางการเป็นเรื่องยาก กล่าวโดยสรุปคือ โปรแกรมแยกวิเคราะห์ HTML นั้นไม่สามารถแยกวิเคราะห์ HTML ได้โดยง่าย เนื่องจากไวยากรณ์นั้นไม่มีบริบท ไม่สามารถแยกวิเคราะห์ HTML ด้วยโปรแกรมแยกวิเคราะห์ XML
DTD แบบ HTML
คำจำกัดความ HTML อยู่ในรูปแบบ DTD รูปแบบนี้ใช้เพื่อกำหนดภาษาในกลุ่ม SGML รูปแบบนี้ประกอบด้วยคำนิยามขององค์ประกอบที่อนุญาต แอตทริบิวต์ และลำดับชั้น ดังที่เราเห็นก่อนหน้านี้ HTML DTD ไม่ได้สร้างไวยากรณ์โดยไม่มีบริบท
DTD มีรูปแบบที่หลากหลาย โหมดเข้มงวดเป็นไปตามข้อกำหนดเฉพาะ แต่โหมดอื่นๆ จะมีการสนับสนุนมาร์กอัปที่เบราว์เซอร์เคยใช้ในอดีต จุดประสงค์คือความเข้ากันได้แบบย้อนหลังกับเนื้อหาเก่า DTD ที่เข้มงวดในปัจจุบันอยู่ที่นี่ www.w3.org/TR/html4/strict.dtd
DOM
แผนผังเอาต์พุต ("แผนผังแยกวิเคราะห์") เป็นแผนผังขององค์ประกอบ DOM และโหนดแอตทริบิวต์ DOM ย่อมาจาก Document Object Model ซึ่งเป็นการนำเสนอออบเจ็กต์ของเอกสาร HTML และอินเทอร์เฟซขององค์ประกอบ HTML ต่อบุคคลภายนอก เช่น JavaScript
รากของโครงสร้างคือ "Document" ออบเจ็กต์
DOM มีความเกี่ยวข้องเกือบหนึ่งต่อหนึ่งกับมาร์กอัป เช่น
<html>
<body>
<p>
Hello World
</p>
<div> <img src="example.png"/></div>
</body>
</html>
มาร์กอัปนี้จะได้รับการแปลเป็นแผนผัง DOM ต่อไปนี้
![แผนผัง DOM ของมาร์กอัปตัวอย่าง](https://web.dev/static/articles/howbrowserswork/image/dom-tree-the-example-mar-70be67fe14c9a.png?authuser=6&hl=th)
องค์กร W3C เป็นผู้ระบุ DOM เช่นเดียวกับ HTML โปรดดูที่ 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 อัลกอริทึมประกอบด้วย 2 ขั้นตอน ได้แก่ การแปลงข้อมูลเป็นโทเค็นและการสร้างต้นไม้
การแปลงข้อมูลเป็นโทเค็นคือการวิเคราะห์คำศัพท์ที่จะแยกวิเคราะห์อินพุตเป็นโทเค็น โทเค็น HTML ประกอบด้วยแท็กเริ่มต้น แท็กปิดท้าย ชื่อแอตทริบิวต์ และค่าแอตทริบิวต์
เครื่องมือแปลงข้อมูลโทเค็นจะจดจำโทเค็น มอบให้กับตัวสร้างต้นไม้ และใช้อักขระถัดไปเพื่อจำแนกโทเค็นถัดไป และต่อไปเรื่อยๆ จนกว่าจะสิ้นสุดอินพุต
![ขั้นตอนการแยกวิเคราะห์ HTML (นำมาจากข้อกำหนดของ HTML5)](https://web.dev/static/articles/howbrowserswork/image/html-parsing-flow-taken-6118c51b92b56.png?authuser=6&hl=th)
อัลกอริทึมการแปลงเป็นโทเค็น
เอาต์พุตของอัลกอริทึมเป็นโทเค็น HTML อัลกอริทึมจะแสดงเป็นเครื่องสถานะ แต่ละสถานะจะใช้อักขระอย่างน้อย 1 ตัวของสตรีมอินพุต และอัปเดตสถานะถัดไปตามอักขระเหล่านั้น การตัดสินใจจะขึ้นอยู่กับสถานะการแปลงข้อมูลเป็นโทเค็นปัจจุบันและสถานะของโครงสร้างต้นไม้ ซึ่งหมายความว่าอักขระที่ใช้ตัวเดียวกันจะให้ผลลัพธ์ที่แตกต่างกันสำหรับสถานะถัดไปที่ถูกต้อง ขึ้นอยู่กับสถานะปัจจุบัน อัลกอริทึมซับซ้อนเกินกว่าที่จะอธิบายได้ทั้งหมด ดังนั้นมาดูตัวอย่างง่ายๆ ที่จะช่วยให้เราสามารถเข้าใจหลักการได้
ตัวอย่างพื้นฐาน - การแปลง HTML ต่อไปนี้เป็นโทเค็น
<html>
<body>
Hello world
</body>
</html>
สถานะเริ่มต้นคือ "สถานะข้อมูล"
เมื่อพบอักขระ <
สถานะจะเปลี่ยนเป็น "สถานะเปิดของแท็ก"
การใช้อักขระ a-z
จะทำให้เกิดการสร้าง "โทเค็นแท็กเริ่มต้น" สถานะจะเปลี่ยนเป็น "สถานะชื่อแท็ก"
เราจะอยู่ในสถานะนี้จนกว่าจะใช้อักขระ >
ตัว ระบบจะเพิ่มอักขระแต่ละตัวต่อท้ายชื่อโทเค็นใหม่ ในกรณีนี้ โทเค็นที่สร้างขึ้นคือ html
เมื่อถึงแท็ก >
ระบบจะปล่อยโทเค็นปัจจุบันและสถานะจะเปลี่ยนกลับไปเป็น "สถานะข้อมูล"
ระบบจะจัดการแท็ก <body>
โดยใช้ขั้นตอนเดียวกัน
จนถึงตอนนี้มีการปล่อยแท็ก html
และ body
ตอนนี้เรากลับมาที่ "สถานะข้อมูล"
การใช้อักขระ H
ของ Hello world
จะเป็นการสร้างและแสดงโทเค็นอักขระ ซึ่งจะเป็นไปจนกระทั่งถึง <
ของ </body>
เราจะปล่อยโทเค็นอักขระสำหรับอักขระ Hello world
ตัว
ตอนนี้เรากลับมาที่ "สถานะเปิดแท็ก"
การใช้อินพุตถัดไป /
จะสร้าง end tag token
และมีการย้ายไปยัง "สถานะชื่อแท็ก" ขอย้ำอีกครั้งว่าเราจะยังอยู่ในสถานะนี้จนกว่าจะถึง >
จากนั้นโทเค็นแท็กใหม่จะเปิดขึ้นมาและกลับไปที่ "สถานะข้อมูล"
ระบบจะดำเนินการกับอินพุต </html>
เหมือนกรณีก่อนหน้า
![การแปลงตัวอย่างอินพุตเป็นโทเค็น](https://web.dev/static/articles/howbrowserswork/image/tokenizing-example-input-9d0cc36689681.png?authuser=6&hl=th)
อัลกอริทึมการสร้างต้นไม้
เมื่อสร้างโปรแกรมแยกวิเคราะห์แล้ว ระบบจะสร้างออบเจ็กต์เอกสาร ในระหว่างขั้นตอนการสร้างแผนผัง ต้นไม้ DOM ที่มีเอกสารอยู่ในรากจะถูกแก้ไขและจะเพิ่มองค์ประกอบลงในนั้น แต่ละโหนดที่ปล่อยโดยเครื่องมือแปลงข้อมูลเป็นโทเค็นจะได้รับการประมวลผลโดยเครื่องมือสร้างแผนผังต้นไม้ สำหรับแต่ละโทเค็น ข้อมูลจำเพาะจะกำหนดว่าองค์ประกอบใดของ DOM ที่เกี่ยวข้องกับองค์ประกอบดังกล่าว และจะถูกสร้างสำหรับโทเค็นนี้ ระบบจะเพิ่มองค์ประกอบลงในแผนผัง DOM และสแต็กขององค์ประกอบที่เปิดอยู่ด้วย กลุ่มนี้ใช้เพื่อแก้ไขการซ้อนแท็กที่ไม่ตรงกันและแท็กที่ไม่ได้ปิด นอกจากนี้ อัลกอริทึมยังอธิบายว่าเป็นเครื่องสถานะ รัฐเหล่านี้เรียกว่า "โหมดการแทรก"
มาดูขั้นตอนการสร้างต้นไม้สำหรับข้อมูลตัวอย่างกัน
<html>
<body>
Hello world
</body>
</html>
อินพุตในขั้นตอนการสร้างแผนผังต้นไม้เป็นลำดับโทเค็นจากขั้นตอนการแปลงข้อมูลเป็นโทเค็น โหมดแรกคือ "โหมดเริ่มต้น" การรับ "html" โทเค็นจะทําให้เปลี่ยนเป็นโหมด "before html" และประมวลผลโทเค็นอีกครั้งในโหมดนั้น การดำเนินการนี้จะทำให้เกิดการสร้างองค์ประกอบ HTMLHtmlElement ซึ่งจะเพิ่มต่อท้ายออบเจ็กต์เอกสารรูท
สถานะจะเปลี่ยนเป็น "ก่อนศีรษะ" "เนื้อความ" จากนั้นจึงได้รับโทเค็น HTMLHeadElement จะสร้างขึ้นโดยปริยาย แม้ว่าเราจะไม่มี "head" และระบบจะเพิ่มโทเค็นไปยังแผนผัง
ตอนนี้เราจะเปลี่ยนเป็นโหมด "in head" จากนั้นไปที่ "หลัง head" โทเค็นเนื้อหาจะได้รับการประมวลผลอีกครั้ง มีการสร้างและแทรก HTMLBodyElement และจะโอนโหมดไปยัง "inbody"
โทเค็นตัวละครของ "สวัสดีโลก" ได้รับสตริงแล้ว การตั้งค่าแรกจะสร้างและแทรก "ข้อความ" โหนดและอักขระอื่นๆ จะถูกเพิ่มต่อท้ายโหนดนั้น
การรับโทเค็นจุดสิ้นสุดของเนื้อความจะทำให้ระบบโอนไปยังโหมด "หลังเนื้อหา" ตอนนี้เราจะได้รับแท็กปิดท้าย HTML ซึ่งจะย้ายเราไปที่โหมด "หลังเนื้อหา" การรับโทเค็นสิ้นสุดของไฟล์จะเป็นการสิ้นสุดการแยกวิเคราะห์
![โครงสร้างแบบต้นไม้ของตัวอย่าง HTML](https://web.dev/static/articles/howbrowserswork/image/tree-construction-exampl-4e9757a851f96.gif?authuser=6&hl=th)
การดำเนินการเมื่อการแยกวิเคราะห์เสร็จสิ้น
ในขั้นตอนนี้ เบราว์เซอร์จะทำเครื่องหมายเอกสารว่าเป็นแบบอินเทอร์แอกทีฟ และเริ่มแยกวิเคราะห์สคริปต์ที่อยู่ในประเภท "เลื่อนเวลา" โหมด: การทำงานที่ควรดำเนินการหลังจากแยกวิเคราะห์เอกสารแล้ว สถานะเอกสารจะถูกตั้งค่าเป็น "เสร็จสมบูรณ์" และ "การโหลด" เหตุการณ์จะเริ่มทำงาน
คุณสามารถดูอัลกอริทึมฉบับเต็มสำหรับการแปลงข้อมูลเป็นโทเค็นและการสร้างแผนผังต้นไม้ได้ในข้อกำหนด HTML5
เบราว์เซอร์ การยอมรับข้อผิดพลาด
คุณจะไม่ได้รับ "ไวยากรณ์ที่ไม่ถูกต้อง" ในหน้า HTML เบราว์เซอร์จะแก้ไขเนื้อหาที่ไม่ถูกต้องแล้วดำเนินการต่อ
ลองดูตัวอย่าง HTML นี้
<html>
<mytag>
</mytag>
<div>
<p>
</div>
Really lousy HTML
</p>
</html>
ฉันต้องละเมิดกฎประมาณ 1 ล้านข้อ ("mytag" ไม่ใช่แท็กมาตรฐาน มีการซ้อนองค์ประกอบ "p" และ "div" ผิด และอื่นๆ) แต่เบราว์เซอร์ยังคงแสดงอย่างถูกต้องและไม่ได้ร้องเรียน ดังนั้น โค้ดโปรแกรมแยกวิเคราะห์จำนวนมากกำลังแก้ไขข้อผิดพลาดของผู้เขียน HTML
การจัดการข้อผิดพลาดนั้นค่อนข้างมีความสอดคล้องกันในเบราว์เซอร์ แต่ที่น่าแปลกใจก็คือ ข้อผิดพลาดดังกล่าวยังไม่เป็นส่วนหนึ่งของข้อกำหนด HTML เช่นเดียวกับการบุ๊กมาร์กและปุ่มย้อนกลับ เป็นฟีเจอร์ที่พัฒนาขึ้นในเบราว์เซอร์ในช่วงหลายปีที่ผ่านมา พบโครงสร้าง HTML ที่ไม่ถูกต้องซึ่งเกิดขึ้นซ้ำๆ ในหลายเว็บไซต์ และเบราว์เซอร์จะพยายามแก้ไขให้ตรงกันกับเบราว์เซอร์อื่นๆ
ข้อกำหนดของ HTML5 มีการกำหนดข้อกำหนดเหล่านี้บางประการ (WebKit สรุปข้อมูลนี้อย่างชัดเจนในความคิดเห็นที่ตอนต้นของคลาสโปรแกรมแยกวิเคราะห์ HTML)
โปรแกรมแยกวิเคราะห์จะแยกวิเคราะห์อินพุตที่แปลงเป็นโทเค็นในเอกสาร ซึ่งเป็นการสร้างแผนผังเอกสาร หากเอกสารมีรูปแบบที่ถูกต้อง การแยกวิเคราะห์จะตรงไปตรงมา
เราต้องขออภัยที่ต้องจัดการกับเอกสาร HTML จำนวนมาก ซึ่งมีรูปแบบไม่ถูกต้อง โปรแกรมแยกวิเคราะห์จึงต้องยอมรับข้อผิดพลาด
เราต้องดูแลเงื่อนไขข้อผิดพลาดต่อไปนี้เป็นอย่างน้อย
- องค์ประกอบที่เพิ่มไม่ได้รับอนุญาตอย่างชัดเจนภายในแท็กด้านนอกบางแท็ก ในกรณีนี้ เราควรปิดแท็กทั้งหมดจนถึงแท็กที่ห้ามไม่ให้มีองค์ประกอบ แล้วเพิ่มในภายหลัง
- เราไม่ได้รับอนุญาตให้เพิ่มองค์ประกอบโดยตรง อาจเป็นไปได้ว่าคนเขียนเอกสารลืมแท็กบางส่วน (หรืออาจมีแท็กแทรกระหว่างกลาง) ซึ่งอาจรวมถึงแท็ก HTML HEAD BODY TBODY TR TD LI (ฉันลืมอะไรไปหรือเปล่า)
- เราต้องการเพิ่มองค์ประกอบบล็อกภายในองค์ประกอบในบรรทัด ปิดองค์ประกอบในบรรทัดทั้งหมดจนถึงองค์ประกอบบล็อกที่อยู่สูงกว่ารายการถัดไป
- หากไม่ได้ผล ให้ปิดองค์ประกอบจนกว่าเราจะอนุญาตให้เพิ่มองค์ประกอบนั้น หรือละเว้นแท็ก
ดูตัวอย่างการยอมรับข้อผิดพลาด WebKit บางส่วน
</br>
จากราคาเต็ม <br>
บางเว็บไซต์ใช้ </br>
แทน <br>
WebKit จะทำงานในลักษณะเดียวกับ <br>
เพื่อให้เข้ากันได้กับ IE และ Firefox
โค้ด
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 จะเปลี่ยนลำดับชั้นเป็นตารางข้างเคียง 2 ตารางดังนี้
<table>
<tr><td>outer table</td></tr>
</table>
<table>
<tr><td>inner table</td></tr>
</table>
โค้ด
if (m_inStrayTableContent && localName == tableTag)
popBlock(tableTag);
WebKit ใช้กลุ่มสำหรับเนื้อหาปัจจุบันขององค์ประกอบ ซึ่งจะดึงตารางด้านในออกจากกลุ่มตารางด้านนอก ตารางจะเป็นแบบข้างเคียง
องค์ประกอบแบบฟอร์มที่ซ้อนกัน
ในกรณีที่ผู้ใช้วางแบบฟอร์มในแบบฟอร์มอื่น ระบบจะไม่สนใจแบบฟอร์มที่ 2
โค้ด
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;
ดังนั้นผู้เขียนเว็บจึงต้องระวัง ให้เขียน HTML ที่มีรูปแบบที่ดี เว้นเสียแต่ว่าคุณต้องการปรากฏเป็นตัวอย่างในข้อมูลโค้ดการยอมรับข้อผิดพลาด WebKit
การแยกวิเคราะห์ 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 ต่อไปนี้
โปรแกรมแยกวิเคราะห์ CSS ของ WebKit
WebKit ใช้โปรแกรมแยกวิเคราะห์ Flex และ Bison เพื่อสร้างโปรแกรมแยกวิเคราะห์โดยอัตโนมัติจากไฟล์ไวยากรณ์ CSS จากข้อมูลเบื้องต้นเกี่ยวกับโปรแกรมแยกวิเคราะห์ Bison จะสร้างโปรแกรมแยกวิเคราะห์ที่ลด Shift ล่างขึ้น Firefox ใช้โปรแกรมแยกวิเคราะห์จากด้านบนลงล่างซึ่งเขียนด้วยตนเอง ในทั้งสองกรณี ไฟล์ CSS แต่ละไฟล์จะได้รับการแยกวิเคราะห์เป็นออบเจ็กต์ StyleSheet แต่ละออบเจ็กต์มีกฎ CSS ออบเจ็กต์กฎ CSS ประกอบด้วยตัวเลือกและออบเจ็กต์การประกาศ และออบเจ็กต์อื่นๆ ที่สอดคล้องกับไวยากรณ์ CSS
![กำลังแยกวิเคราะห์ CSS](https://web.dev/static/articles/howbrowserswork/image/parsing-css-4531ebee58764.png?authuser=6&hl=th)
ลำดับการประมวลผลสำหรับสคริปต์และสไตล์ชีต
สคริปต์
โมเดลของเว็บเป็นแบบซิงโครนัส ผู้เขียนคาดหวังให้สคริปต์แยกวิเคราะห์และดำเนินการทันทีเมื่อโปรแกรมแยกวิเคราะห์เข้าถึงแท็ก <script>
การแยกวิเคราะห์เอกสารจะหยุดลงจนกว่าจะมีการเรียกใช้สคริปต์
หากสคริปต์อยู่ภายนอก จะต้องมีการดึงข้อมูลทรัพยากรจากเครือข่ายก่อน ซึ่งจะทำแบบซิงโครนัสด้วยเช่นกัน และการแยกวิเคราะห์จะหยุดลงจนกว่าจะดึงข้อมูลทรัพยากร
เครื่องมือนี้เป็นต้นแบบมาหลายปีแล้ว และยังระบุไว้ในข้อกำหนด HTML4 และ 5 ด้วย
ผู้เขียนสามารถเพิ่ม "เลื่อน" ให้กับสคริปต์ ซึ่งในกรณีนี้ ฟังก์ชันจะไม่หยุดการแยกวิเคราะห์เอกสาร และจะทำงานหลังจากแยกวิเคราะห์เอกสารแล้ว HTML5 เพิ่มตัวเลือกเพื่อทำเครื่องหมายสคริปต์เป็นอะซิงโครนัสเพื่อแยกวิเคราะห์และดำเนินการตามเธรดอื่น
การแยกวิเคราะห์แบบคาดเดา
ทั้ง WebKit และ Firefox ต่างก็ดำเนินการเพิ่มประสิทธิภาพนี้ ขณะเรียกใช้สคริปต์ เทรดอื่นจะแยกวิเคราะห์ส่วนที่เหลือของเอกสาร และดูว่าทรัพยากรใดที่ต้องโหลดจากเครือข่ายและโหลดทรัพยากรเหล่านั้น ทำให้โหลดทรัพยากรได้เมื่อเชื่อมต่อแบบขนานและเพิ่มความเร็วโดยรวม หมายเหตุ: โปรแกรมแยกวิเคราะห์แบบคาดเดาจะแยกวิเคราะห์การอ้างอิงไปยังทรัพยากรภายนอกเท่านั้น เช่น สคริปต์ภายนอก สไตล์ชีต และรูปภาพ โดยไม่ได้แก้ไขแผนผัง DOM ที่เหลืออยู่สำหรับโปรแกรมแยกวิเคราะห์หลัก
สไตล์ชีต
ในทางกลับกัน สไตล์ชีตมีรุ่นอื่น โดยหลักการแล้ว ดูเหมือนว่าสไตล์ชีตจะไม่เปลี่ยนโครงสร้าง DOM จึงไม่มีเหตุผลที่จะต้องรอและหยุดการแยกวิเคราะห์เอกสาร แต่มีปัญหาเกิดขึ้นกับสคริปต์ที่ขอข้อมูลรูปแบบในระหว่างขั้นตอนการแยกวิเคราะห์เอกสาร หากยังไม่ได้โหลดและแยกวิเคราะห์รูปแบบ สคริปต์จะได้รับคำตอบที่ไม่ถูกต้อง ซึ่งเห็นได้ชัดว่าทำให้เกิดปัญหาจำนวนมาก ดูเหมือนว่านี่จะเป็นเคส Edge Case แต่ค่อนข้างเกิดขึ้นบ่อย Firefox จะบล็อกสคริปต์ทั้งหมดเมื่อมีสไตล์ชีตที่ยังโหลดและแยกวิเคราะห์อยู่ WebKit จะบล็อกสคริปต์เฉพาะเมื่อพยายามเข้าถึงคุณสมบัติบางอย่างของสไตล์ที่อาจได้รับผลกระทบจากสไตล์ชีตที่ไม่ได้โหลด
แสดงผลการสร้างแบบต้นไม้
ขณะที่สร้างแผนผัง DOM เบราว์เซอร์จะสร้างแผนผังอื่น ซึ่งก็คือแผนผังแสดงผล แผนผังนี้เป็นองค์ประกอบภาพตามลำดับที่จะแสดง ซึ่งเป็นภาพนำเสนอของเอกสาร วัตถุประสงค์ของโครงสร้างต้นไม้นี้คือการระบายสีเนื้อหาตามลำดับที่ถูกต้อง
Firefox เรียกองค์ประกอบในแผนผังแสดงผลว่า "Frame" WebKit ใช้คำว่า Renderer หรือออบเจ็กต์การแสดงผล
โหมดแสดงภาพรู้วิธีการจัดวางและระบายสีตัวเองและลูกๆ
คลาส 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 ซึ่งประกอบด้วยข้อมูลเรขาคณิต เช่น ความกว้าง ความสูง และตำแหน่ง
ประเภทกล่องได้รับผลกระทบจาก "การแสดงผล" ของแอตทริบิวต์รูปแบบที่เกี่ยวข้องกับโหนด (ดูส่วน style computation) นี่คือโค้ด WebKit สำหรับการตัดสินใจว่าควรสร้างโหมดแสดงภาพประเภทใดสำหรับโหนด DOM ตามแอตทริบิวต์ display
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 แต่ความสัมพันธ์ไม่ได้เป็นแบบ 1 ต่อ 1 ระบบจะไม่แทรกองค์ประกอบ DOM ที่ไม่มีภาพในแผนผังแสดงผล เช่น "head" นอกจากนี้ องค์ประกอบที่มีค่าการแสดงผลเป็น "none" จะไม่ปรากฏในแผนผัง (ในขณะที่องค์ประกอบที่มีการเปิดเผย "ซ่อนอยู่" จะปรากฏในโครงสร้าง)
มีองค์ประกอบ DOM ที่สอดคล้องกับออบเจ็กต์ภาพหลายรายการ ซึ่งมักเป็นองค์ประกอบที่มีโครงสร้างซับซ้อนที่ไม่สามารถอธิบายได้ด้วยสี่เหลี่ยมผืนผ้าเพียงอันเดียว ตัวอย่างเช่น "การเลือก" องค์ประกอบจะมี 3 โหมด คือ แผงหนึ่งสำหรับพื้นที่แสดงผล ส่วนแรกสำหรับช่องรายการแบบเลื่อนลง ส่วนอีกรายการสำหรับปุ่ม นอกจากนี้ เมื่อข้อความถูกแยกเป็นหลายบรรทัดเนื่องจากความกว้างไม่เพียงพอสำหรับบรรทัดเดียว บรรทัดใหม่จะถูกเพิ่มเป็นโหมดแสดงภาพเพิ่มเติม
อีกตัวอย่างหนึ่งของเครื่องมือแสดงภาพหลายตัวคือ HTML ที่เสียหาย ตามข้อกำหนด CSS องค์ประกอบในบรรทัดต้องมีเฉพาะองค์ประกอบบล็อก หรือเฉพาะองค์ประกอบในบรรทัด ในกรณีที่มีเนื้อหาผสม ระบบจะสร้างตัวแสดงผลบล็อกแบบไม่ระบุชื่อเพื่อรวมองค์ประกอบในบรรทัด
ออบเจ็กต์การแสดงผลบางรายการสอดคล้องกับโหนด DOM แต่ไม่อยู่ในตำแหน่งเดียวกันในโครงสร้าง ทศนิยมและองค์ประกอบที่อยู่ในตำแหน่งที่แน่นอนไม่มีการไหล วางไว้ในส่วนอื่นของต้นไม้ และจับคู่กับเฟรมจริง โดยมีเฟรมตัวยึดตำแหน่งเป็นที่ที่ควรอยู่
![แผนผังการแสดงผลและแผนผัง DOM ที่เกี่ยวข้อง](https://web.dev/static/articles/howbrowserswork/image/the-render-tree-the-corr-f699894ef4c75.png?authuser=6&hl=th)
ขั้นตอนการสร้างต้นไม้
ใน Firefox งานนำเสนอจะได้รับการบันทึกเป็น Listener สำหรับการอัปเดต DOM
งานนำเสนอมอบสิทธิ์การสร้างเฟรมให้กับ FrameConstructor
และเครื่องมือสร้างแก้ไขรูปแบบ (ดูการคำนวณรูปแบบ) และสร้างเฟรม
ใน WebKit กระบวนการแก้ไขรูปแบบและการสร้างโหมดแสดงภาพเรียกว่า "ไฟล์แนบ" โหนด DOM ทุกโหนดมี "attach" ไฟล์แนบเป็นแบบซิงโครนัส การแทรกโหนดกับแผนผัง DOM จะเรียกโหนดใหม่ว่า "Attach"
การประมวลผลแท็ก HTML และแท็กเนื้อหาจะทำให้เกิดการสร้างรากแผนผังการแสดงผล
ออบเจ็กต์การแสดงผลระดับรูทสอดคล้องกับสิ่งที่ข้อกำหนดของ CSS เรียกใช้บล็อกที่มี ซึ่งก็คือบล็อกบนสุดที่มีบล็อกอื่นๆ ทั้งหมด โดยขนาดคือวิวพอร์ต: มิติข้อมูลพื้นที่แสดงหน้าต่างเบราว์เซอร์
Firefox เรียกสิ่งนี้ว่า ViewPortFrame
และ WebKit เรียกสิ่งนี้ว่า RenderView
นี่คือออบเจ็กต์การแสดงผลที่เอกสารชี้ไป
ส่วนที่เหลือของโครงสร้างสร้างขึ้นเป็นการแทรกโหนด DOM
ดูข้อกำหนดของ CSS2 เกี่ยวกับโมเดลการประมวลผล
การคำนวณรูปแบบ
การสร้างแผนผังการแสดงผลจำเป็นต้องมีการคำนวณคุณสมบัติการมองเห็นของออบเจ็กต์การแสดงผลแต่ละรายการ ซึ่งทำได้โดยการคำนวณคุณสมบัติรูปแบบของแต่ละองค์ประกอบ
สไตล์นี้รวมถึงสไตล์ชีตของต้นทางต่างๆ องค์ประกอบของสไตล์แบบอินไลน์ และคุณสมบัติที่มองเห็นใน HTML (เช่น คุณสมบัติ "bgcolor") ระบบจะแปลค่าภายหลังเป็นคุณสมบัติสไตล์ CSS ที่ตรงกัน
ต้นทางของสไตล์ชีตคือสไตล์ชีตเริ่มต้นของเบราว์เซอร์ สไตล์ชีตที่ผู้เขียนหน้าเว็บ และสไตล์ชีตของผู้ใช้จัดเตรียมไว้ ซึ่งเป็นสไตล์ชีตที่ผู้ใช้เบราว์เซอร์มีให้ (เบราว์เซอร์ช่วยให้คุณกำหนดสไตล์ที่ชื่นชอบได้ ตัวอย่างเช่น ใน Firefox นั้น สามารถทำได้โดยวางสไตล์ชีตใน "โปรไฟล์ Firefox" )
การคำนวณรูปแบบจะทำให้เกิดปัญหา 2-3 ข้อ
- ข้อมูลรูปแบบเป็นโครงสร้างที่ใหญ่มากและมีคุณสมบัติของรูปแบบจำนวนมาก จึงอาจทำให้เกิดปัญหาเกี่ยวกับหน่วยความจำ
การค้นหากฎการจับคู่ของแต่ละองค์ประกอบอาจทำให้เกิดปัญหาด้านประสิทธิภาพได้หากไม่มีการเพิ่มประสิทธิภาพ การเลื่อนดูรายการกฎทั้งหมดสำหรับแต่ละองค์ประกอบเพื่อค้นหารายการที่ตรงกันเป็นงานที่หนัก ตัวเลือกอาจมีโครงสร้างที่ซับซ้อนซึ่งอาจทำให้กระบวนการจับคู่เริ่มขึ้นบนเส้นทางที่ดูน่าสนใจ และพิสูจน์แล้วว่าไม่มีประโยชน์และต้องลองใช้เส้นทางอื่น
เช่น ตัวเลือกแบบผสมนี้
div div div div{ ... }
หมายความว่ากฎจะมีผลกับ
<div>
ที่สืบทอดจาก div 3 รายการ สมมติว่าคุณต้องการตรวจสอบว่ากฎมีผลกับองค์ประกอบ<div>
ที่ระบุหรือไม่ คุณเลือกเส้นทางที่เจาะจงในโครงสร้างสำหรับการตรวจสอบ คุณอาจต้องข้ามโหนดทรีขึ้นเพื่อดูว่ามีเพียง div เพียง 2 รายการและกฎใช้ไม่ได้ จากนั้นคุณต้องลองเส้นทางอื่นในต้นไม้นี้การใช้กฎประกอบด้วยกฎแบบเรียงซ้อนที่ค่อนข้างซับซ้อนซึ่งกำหนดลำดับชั้นของกฎ
เรามาดูกันว่าเบราว์เซอร์ต่างๆ พบปัญหาเหล่านี้อย่างไร
การแชร์ข้อมูลรูปแบบ
โหนด WebKit อ้างอิงออบเจ็กต์รูปแบบ (RenderStyle) ออบเจ็กต์เหล่านี้สามารถแชร์โดยโหนดได้ในบางเงื่อนไข โหนดคือพี่น้องหรือญาติๆ และ:
- องค์ประกอบต้องอยู่ในสถานะเดียวกันของเมาส์ (เช่น องค์ประกอบหนึ่งต้องไม่อยู่ใน :hover ขณะที่อีกองค์ประกอบหนึ่งไม่อยู่ใน)
- แต่ละองค์ประกอบไม่ควรมีรหัส
- ชื่อแท็กควรตรงกับ
- แอตทริบิวต์คลาสควรตรงกับ
- ชุดแอตทริบิวต์ที่แมปต้องเหมือนกัน
- สถานะลิงก์ต้องตรงกัน
- สถานะโฟกัสต้องตรงกัน
- องค์ประกอบต่างๆ ไม่ควรได้รับผลกระทบจากตัวเลือกแอตทริบิวต์ โดยองค์ประกอบที่ได้รับผลกระทบจะได้รับการระบุว่ามีการจับคู่ตัวเลือกที่ใช้ตัวเลือกแอตทริบิวต์ในตำแหน่งใดๆ ภายในตัวเลือกเลย
- ต้องไม่มีแอตทริบิวต์รูปแบบแทรกในบรรทัดในองค์ประกอบ
- ต้องไม่มีตัวเลือกข้างเคียงที่ใช้งานอยู่เลย WebCore จะสลับสวิตช์ส่วนกลางเมื่อพบตัวเลือกข้างเคียงและปิดใช้การแชร์รูปแบบสำหรับเอกสารทั้งฉบับเมื่อมีตัวเลือกดังกล่าว ซึ่งรวมถึงตัวเลือก + และตัวเลือก เช่น :first-child และ :last-child
แผนผังกฎของ Firefox
Firefox มีต้นไม้พิเศษอีก 2 ต้นเพื่อการคำนวณรูปแบบที่ง่ายขึ้น ซึ่งก็คือแผนผังกฎและแผนผังบริบทของรูปแบบ WebKit ยังมีออบเจ็กต์รูปแบบ แต่ไม่ได้จัดเก็บไว้ในโครงสร้าง เช่น แผนผังบริบทของรูปแบบ แต่เฉพาะโหนด DOM เท่านั้นที่จะชี้ไปยังรูปแบบที่เกี่ยวข้อง
![แผนผังบริบทของรูปแบบ Firefox](https://web.dev/static/articles/howbrowserswork/image/firefox-style-context-tre-f578b75b74df7.png?authuser=6&hl=th)
บริบทของรูปแบบมีค่าสิ้นสุด ค่าต่างๆ จะคำนวณโดยใช้กฎการจับคู่ทั้งหมดในลำดับที่ถูกต้อง และดำเนินการปรับเปลี่ยนจากค่าตรรกะเป็นค่าที่เป็นรูปธรรม เช่น หากค่าตรรกะเป็นเปอร์เซ็นต์ของหน้าจอ ระบบจะคำนวณและเปลี่ยนรูปแบบเป็นหน่วยสัมบูรณ์ ไอเดียแผนผังกฎนี้ฉลาดมาก ซึ่งจะทำให้แชร์ค่าเหล่านี้ระหว่างโหนดได้เพื่อหลีกเลี่ยงการประมวลผลค่าดังกล่าวอีกครั้ง ซึ่งจะช่วยประหยัดพื้นที่ด้วย
กฎที่ตรงกันทั้งหมดจะเก็บอยู่ในแผนผัง โหนดด้านล่างในเส้นทางมีลำดับความสำคัญสูงกว่า แผนผังประกอบด้วยเส้นทางทั้งหมดสำหรับกฎที่ตรงกันที่พบ การเก็บกฎจะเป็นแบบ Lazy Loading ระบบจะไม่คำนวณแผนผังนี้ที่จุดเริ่มต้นของทุกโหนด แต่เมื่อใดก็ตามที่จำเป็นต้องคำนวณรูปแบบโหนด เส้นทางที่คำนวณแล้วจะเพิ่มลงในโครงสร้างดังกล่าว
แนวคิดคือให้ดูเส้นทางบนต้นไม้ในรูปแบบคำศัพท์ สมมติว่าเราคำนวณโครงสร้างกฎนี้แล้ว:
![แผนผังกฎที่คำนวณแล้ว](https://web.dev/static/articles/howbrowserswork/image/computed-rule-tree-f874f412bbaf.png?authuser=6&hl=th)
สมมติว่าเราต้องจับคู่กฎสำหรับองค์ประกอบอื่นในโครงสร้างเนื้อหา และค้นหากฎที่ตรงกัน (ตามลำดับที่ถูกต้อง) คือ B-E-I เรามีเส้นทางนี้ในโครงสร้างอยู่แล้ว เนื่องจากเราคำนวณเส้นทาง A-B-E-I-L แล้ว เราจะมีงานต้องทำน้อยลง
มาดูกันว่าต้นไม้ช่วยให้เราทำงานได้อย่างไร
การแบ่งออกเป็นโครงสร้าง
บริบทของรูปแบบจะแบ่งออกเป็น Struct โครงสร้างดังกล่าวมีข้อมูลรูปแบบสำหรับบางหมวดหมู่ เช่น เส้นขอบหรือสี พร็อพเพอร์ตี้ทั้งหมดในโครงสร้างเป็นแบบรับค่าเดิมหรือไม่รับค่าเดิม พร็อพเพอร์ตี้ที่รับช่วงมาคือพร็อพเพอร์ตี้ที่องค์ประกอบจะรับค่าจากระดับบน เว้นแต่จะกำหนดโดยองค์ประกอบ พร็อพเพอร์ตี้ที่รับค่ามา (เรียกว่าพร็อพเพอร์ตี้ "รีเซ็ต") จะใช้ค่าเริ่มต้น หากไม่ได้กำหนดไว้
ต้นไม้ช่วยเราโดยการแคชโครงสร้างทั้งหมด (ซึ่งมีค่าสิ้นสุดที่คำนวณ) ในต้นไม้ แนวคิดก็คือหากโหนดด้านล่างไม่ได้ระบุคำจำกัดความของโครงสร้าง ก็สามารถใช้โครงสร้างที่แคชไว้ในโหนดด้านบนได้
การคำนวณบริบทของรูปแบบโดยใช้แผนผังกฎ
ขณะคำนวณบริบทของรูปแบบสำหรับองค์ประกอบบางรายการ เราจะคำนวณเส้นทางในโครงสร้างกฎหรือใช้เส้นทางที่มีอยู่ก่อน จากนั้นเราจะเริ่มใช้กฎในเส้นทางเพื่อเติม Struct ในบริบทของรูปแบบใหม่ของเรา เราเริ่มต้นที่โหนดด้านล่างของเส้นทาง - โหนดที่มีความสำคัญสูงสุด (โดยปกติจะเป็นตัวเลือกที่เฉพาะเจาะจงมากที่สุด) และข้ามผ่านต้นไม้ไปจนกระทั่งโครงสร้างของเราเต็ม หากไม่มีข้อมูลจำเพาะสำหรับโครงสร้างในโหนดของกฎนั้น เราจะเพิ่มประสิทธิภาพได้อย่างมาก โดยเราจะขึ้นไปบนต้นไม้จนกว่าจะพบโหนดที่ระบุโหนดอย่างครบถ้วนและชี้เมาส์ไปที่โหนด ซึ่งเป็นการเพิ่มประสิทธิภาพที่ดีที่สุด จะมีการแชร์โครงสร้างทั้งหมด ซึ่งจะช่วยประหยัดการคำนวณค่าสุดท้ายและหน่วยความจำ
หากเราพบคำนิยามบางส่วน เราจะขึ้นโครงสร้างจนกว่าจะเติมโครงสร้าง
หากไม่พบคำนิยามใดๆ สำหรับ Struct ของเรา ในกรณีที่ Struct เป็นแบบ "รับค่าเดิมมา" เราจะชี้เมาส์ไปที่โครงสร้างระดับบนสุดในแผนผังบริบท ในกรณีนี้ เราก็ประสบความสำเร็จในการแชร์ Struct ด้วย หากเป็นโครงสร้างการรีเซ็ต ระบบจะใช้ค่าเริ่มต้น
ถ้าโหนดที่เฉพาะเจาะจงมากที่สุดมีการเพิ่มค่า เราจำเป็นต้องทำการคำนวณเพิ่มเติมเพื่อเปลี่ยนค่าให้เป็นค่าจริง จากนั้นเราจะแคชผลลัพธ์ในโหนดแผนผังเพื่อให้เด็กนำไปใช้ได้
ในกรณีที่องค์ประกอบมีพี่น้องหรือพี่ชายที่ชี้ไปยังโหนดต้นไม้เดียวกัน ก็สามารถแชร์บริบททั้งหมดระหว่างองค์ประกอบได้
ลองดูตัวอย่างต่อไปนี้ สมมติว่าเรามี 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}
เพื่อให้ง่ายขึ้น สมมติว่าเราต้องกรอกข้อมูล 2 โครงสร้างเท่านั้น ได้แก่ โครงสร้างสีและโครงสร้างระยะขอบ โครงสร้างสีมีเพียงสมาชิกเดียว: สี โครงสร้างขอบประกอบด้วยทั้ง 4 ด้าน
แผนผังกฎที่ได้จะมีลักษณะดังนี้ (โหนดจะถูกทำเครื่องหมายด้วยชื่อโหนด ซึ่งก็คือหมายเลขของกฎที่จุดนั้นชี้ไป):
![แผนผังกฎ](https://web.dev/static/articles/howbrowserswork/image/the-rule-tree-23f05b0dac33f.png?authuser=6&hl=th)
แผนผังบริบทจะมีลักษณะดังนี้ (ชื่อโหนด: โหนดกฎที่โหนดชี้ไปยัง):
![แผนผังบริบท](https://web.dev/static/articles/howbrowserswork/image/the-context-tree-771124b7cb80d.png?authuser=6&hl=th)
สมมติว่าเราแยกวิเคราะห์ HTML และไปที่แท็ก <div>
ที่สอง เราต้องสร้างบริบทรูปแบบสำหรับโหนดนี้และเติมโครงสร้างสไตล์ของโหนด
เราจะจับคู่กฎต่างๆ และพบว่ากฎการจับคู่สำหรับ <div>
คือ 1, 2 และ 6
ซึ่งหมายความว่ามีเส้นทางอยู่แล้วในโครงสร้างที่องค์ประกอบของเราสามารถใช้ได้ และเราเพียงต้องเพิ่มโหนดอีกโหนดสำหรับกฎ 6 (โหนด F ในโครงสร้างกฎ)
เราจะสร้างบริบทรูปแบบและใส่ไว้ในแผนผังบริบท บริบทของรูปแบบใหม่จะชี้ไปยังโหนด F ในโครงสร้างกฎ
ตอนนี้เราจำเป็นต้องเติม Struct ของรูปแบบ เราจะเริ่มต้นด้วยการกรอกข้อมูลโครงสร้างขอบ เนื่องจากโหนดกฎล่าสุด (F) ไม่ได้เพิ่มเข้าไปในโครงสร้างระยะขอบ เราจึงสามารถขึ้นโครงสร้างไปจนพบโครงสร้างที่แคชไว้ซึ่งคำนวณในการแทรกโหนดก่อนหน้าและนำมาใช้ เราจะหาค่านี้ในโหนด B ซึ่งเป็นโหนดบนสุดที่ระบุกฎส่วนต่างกำไร
เรามีการกำหนดโครงสร้างสี ดังนั้นจึงใช้โครงสร้างที่แคชไว้ไม่ได้ เนื่องจากสีมีแอตทริบิวต์ 1 ตัว เราจึงไม่จำเป็นต้องขึ้นต้นไม้ไปเติมแอตทริบิวต์อื่นๆ เราจะคำนวณค่าสิ้นสุด (แปลงสตริงเป็น RGB เป็นต้น) และแคชโครงสร้างที่คำนวณในโหนดนี้
การทำงานในองค์ประกอบ <span>
รายการที่ 2 จะง่ายขึ้นอีก เราจะจับคู่กฎเหล่านั้นและสรุปว่ากฎข้อนั้นชี้ไปที่กฎ G เช่นช่วงก่อนหน้า
เนื่องจากเรามีกลุ่มพี่น้องที่ชี้ไปยังโหนดเดียวกัน เราจึงแชร์บริบทของสไตล์ทั้งหมดและชี้ไปยังบริบทของช่วงเวลาก่อนหน้าได้
สำหรับ Struct ที่มีกฎที่สืบทอดมาจากระดับบนสุด การแคชจะดำเนินการบนแผนผังบริบท (จริงๆ แล้ว พร็อพเพอร์ตี้สีสืบทอดค่ามา แต่ Firefox จะถือว่านี่เป็นการรีเซ็ตและแคชข้อมูลนั้นในแผนผังกฎ)
ตัวอย่างเช่น หากเราเพิ่มกฎสำหรับแบบอักษรในย่อหน้า
p {font-family: Verdana; font size: 10px; font-weight: bold}
แล้วองค์ประกอบย่อหน้า ซึ่งเป็นองค์ประกอบย่อยของ div ในโครงสร้างบริบท สามารถใช้โครงสร้างแบบอักษรเดียวกันกับองค์ประกอบหลัก กล่าวคือหากไม่ได้ระบุกฎแบบอักษรสำหรับย่อหน้า
ใน WebKit ซึ่งไม่มีแผนผังกฎ ระบบจะข้ามการประกาศที่ตรงกัน 4 ครั้ง โดยจะใช้พร็อพเพอร์ตี้ที่มีลำดับความสำคัญสูงที่ไม่สำคัญก่อน (พร็อพเพอร์ตี้ที่ควรนำมาใช้ก่อนเนื่องจากพร็อพเพอร์ตี้อื่นๆ ขึ้นอยู่กับพร็อพเพอร์ตี้เหล่านั้น เช่น โฆษณาแบบดิสเพลย์) จากนั้นจึงใช้พร็อพเพอร์ตี้ที่มีลำดับความสำคัญสูง กฎที่มีลำดับความสำคัญสูงที่ไม่สำคัญ และกฎที่มีลำดับความสำคัญปกติ ซึ่งหมายความว่าพร็อพเพอร์ตี้ที่ปรากฏหลายครั้งจะได้รับการแก้ไขตามลำดับแบบ Cascade ที่ถูกต้อง ผู้ชนะคนสุดท้าย
สรุปก็คือการแชร์ออบเจ็กต์รูปแบบ (ทั้ง Struct ภายในหรือทั้งหมด) ช่วยแก้ปัญหาที่ 1 และ 3 แผนผังกฎของ Firefox ยังช่วยในการนำคุณสมบัติไปใช้ตามลำดับที่ถูกต้อง
การจัดการกฎเพื่อให้จับคู่ได้ง่าย
กฎสไตล์มีแหล่งที่มาหลายแห่ง ดังนี้
- กฎ 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}
ระบบจะแทรกกฎข้อแรกลงในแมปชั้นเรียน รายการที่ 2 จะอยู่ในแมปรหัส และส่วนที่ 3 ในแมปแท็ก
สำหรับส่วนย่อย HTML ต่อไปนี้
<p class="error">an error occurred</p>
<div id=" messageDiv">this is a message</div>
เราจะพยายามค้นหากฎสำหรับองค์ประกอบ p ก่อน แมปชั้นเรียนจะมี "ข้อผิดพลาด" ที่อยู่ภายใต้กฎสำหรับ "p.error" พบ องค์ประกอบ div จะมีกฎที่เกี่ยวข้องในแมปรหัส (คีย์คือ id) และแมปแท็ก ดังนั้นงานเดียวที่เหลืออยู่คือการค้นหากฎที่คีย์ดึงข้อมูลมาได้ตรงกันจริงๆ
เช่น ถ้ากฎสำหรับ div คือ
table div {margin: 5px}
คีย์จะยังคงดึงข้อมูลจากการแมปแท็ก เนื่องจากคีย์เป็นตัวเลือกด้านขวาสุด แต่จะไม่ตรงกับองค์ประกอบ div ของเราที่ไม่มีระดับบนของตาราง
โดยทั้ง WebKit และ Firefox จะทำการควบคุมนี้
ลำดับแบบต่อเรียงของสไตล์ชีต
ออบเจ็กต์รูปแบบมีพร็อพเพอร์ตี้ที่เกี่ยวข้องกับแอตทริบิวต์ภาพทั้งหมด (แอตทริบิวต์ CSS ทั้งหมดแต่มีความเฉพาะเจาะจงมากกว่า) หากกฎที่ตรงกันไม่ได้กำหนดพร็อพเพอร์ตี้ พร็อพเพอร์ตี้บางรายการสามารถรับค่าจากออบเจ็กต์รูปแบบองค์ประกอบระดับบนสุดได้ ส่วนพร็อพเพอร์ตี้อื่นๆ จะมีค่าเริ่มต้น
ปัญหาจะเริ่มเมื่อมีคำจำกัดความมากกว่า 1 แบบ มาดูตามลำดับแบบ Cascade เพื่อแก้ไขปัญหา
การประกาศสำหรับพร็อพเพอร์ตี้สไตล์อาจปรากฏในสไตล์ชีตหลายรายการ และหลายครั้งภายในสไตล์ชีต ซึ่งหมายความว่าลำดับของการใช้กฎมีความสำคัญมาก น้ำตกนี้เรียกว่า "น้ำตก" คำสั่งซื้อ ตามข้อกำหนดของ CSS2 ลำดับแบบ Cascade คือ (จากต่ำไปสูง) ดังนี้
- การประกาศของเบราว์เซอร์
- การประกาศทั่วไปของผู้ใช้
- การประกาศปกติของผู้เขียน
- ประกาศที่สำคัญของผู้เขียน
- ประกาศสำคัญของผู้ใช้
การประกาศของเบราว์เซอร์มีความสําคัญน้อยที่สุดและผู้ใช้จะลบล้างผู้เขียนเมื่อมีการทําเครื่องหมายว่าประกาศว่าสําคัญเท่านั้น ประกาศที่มีลําดับเดียวกันจะจัดเรียงตามความจําเพาะ แล้วจึงระบุลําดับ แอตทริบิวต์ภาพ HTML จะแปลงเป็นการประกาศ CSS ที่ตรงกัน โดยจะถือว่าเป็นกฎที่มีลำดับความสำคัญต่ำ
ลักษณะเฉพาะ
ความจำเพาะของตัวเลือกจะกำหนดโดยข้อกำหนด CSS2 ดังนี้
- นับ 1 หากการประกาศว่าเป็น "สไตล์" แทนที่จะเป็นกฎที่มีตัวเลือก มิฉะนั้นจะเป็น 0 (= a)
- นับจำนวนแอตทริบิวต์รหัสในตัวเลือก (= b)
- นับจำนวนแอตทริบิวต์และคลาสจำลองอื่นๆ ในตัวเลือก (= c)
- นับจำนวนชื่อองค์ประกอบและองค์ประกอบเทียมในตัวเลือก (= d)
การต่อตัวเลข 4 ตัว a-b-c-d (ในระบบตัวเลขที่มีฐานใหญ่) จะช่วยให้ได้ค่าที่เจาะจง
ฐานตัวเลขที่จำเป็นต้องใช้จะกำหนดด้วยจำนวนสูงสุดที่คุณมีในหมวดหมู่ใดหมวดหมู่หนึ่ง
เช่น ถ้า a=14 คุณสามารถใช้ฐานสิบหกได้ ในกรณีที่ไม่เกิดขึ้นบ่อย ซึ่ง a=17 คุณจะต้องใช้ฐานตัวเลข 17 หลัก สถานการณ์ภายหลังอาจเกิดขึ้นได้กับตัวเลือกดังต่อไปนี้ html เนื้อหา div div p... (17 แท็กในตัวเลือกของคุณ... ไม่ค่อยมีแนวโน้ม)
ตัวอย่างมีดังต่อไปนี้
* {} /* a=0 b=0 c=0 d=0 -> specificity = 0,0,0,0 */
li {} /* a=0 b=0 c=0 d=1 -> specificity = 0,0,0,1 */
li:first-line {} /* a=0 b=0 c=0 d=2 -> specificity = 0,0,0,2 */
ul li {} /* a=0 b=0 c=0 d=2 -> specificity = 0,0,0,2 */
ul ol+li {} /* a=0 b=0 c=0 d=3 -> specificity = 0,0,0,3 */
h1 + *[rel=up]{} /* a=0 b=0 c=1 d=1 -> specificity = 0,0,1,1 */
ul ol li.red {} /* a=0 b=0 c=1 d=3 -> specificity = 0,0,1,3 */
li.red.level {} /* a=0 b=0 c=2 d=1 -> specificity = 0,0,2,1 */
#x34y {} /* a=0 b=1 c=0 d=0 -> specificity = 0,1,0,0 */
style="" /* a=1 b=0 c=0 d=0 -> specificity = 1,0,0,0 */
การจัดเรียงกฎ
หลังจากจับคู่กฎแล้ว ระบบจะจัดเรียงตามกฎแบบเรียงซ้อน
WebKit ใช้การจัดเรียงลูกโป่งสำหรับรายการขนาดเล็ก และการผสานการจัดเรียงสำหรับรายการขนาดใหญ่
WebKit ใช้การจัดเรียงโดยลบล้างโอเปอเรเตอร์ >
สำหรับกฎต่างๆ ดังนี้
static bool operator >(CSSRuleData& r1, CSSRuleData& r2)
{
int spec1 = r1.selector()->specificity();
int spec2 = r2.selector()->specificity();
return (spec1 == spec2) : r1.position() > r2.position() : spec1 > spec2;
}
กระบวนการแบบค่อยเป็นค่อยไป
WebKit ใช้แฟล็กที่ทำเครื่องหมายว่าโหลดสไตล์ชีตระดับบนสุดทั้งหมด (รวมถึง @imports) แล้ว ถ้ารูปแบบยังโหลดไม่สมบูรณ์เมื่อแนบ ระบบจะใช้ตัวยึดตำแหน่งและมีการทำเครื่องหมายไว้ในเอกสาร และจะมีการคำนวณใหม่เมื่อโหลดสไตล์ชีตแล้ว
เลย์เอาต์
เมื่อสร้างและเพิ่มโหมดแสดงภาพลงในโครงสร้างแล้ว โหมดแสดงภาพจะไม่มีตำแหน่งและขนาด การคำนวณค่าเหล่านี้เรียกว่าการจัดวางหรือการจัดเรียงใหม่
HTML ใช้รูปแบบการจัดวางตามโฟลว์ ซึ่งหมายความว่าโดยส่วนใหญ่แล้วจะสามารถคำนวณเรขาคณิตในการส่งข้อมูลหนึ่งๆ ได้ องค์ประกอบที่อยู่ภายหลัง "ในโฟลว์" ตามปกติแล้วจะไม่ส่งผลต่อเรขาคณิตขององค์ประกอบที่อยู่ก่อนหน้านี้ "ในโฟลว์" ดังนั้น เลย์เอาต์จึงมีลำดับจากซ้ายไปขวา บนลงล่าง ผ่านทางเอกสารได้ แต่มีข้อยกเว้น เช่น ตาราง HTML อาจต้องผ่านมากกว่า 1 ครั้ง
ระบบพิกัดจะสัมพันธ์กับเฟรมราก ระบบใช้พิกัดด้านบนและด้านซ้าย
เลย์เอาต์เป็นกระบวนการที่เกิดซ้ำ โดยจะเริ่มต้นที่ตัวแสดงผลระดับรูท ซึ่งสอดคล้องกับองค์ประกอบ <html>
ของเอกสาร HTML การจัดวางจะดำเนินต่อไปเรื่อยๆ ผ่านลำดับชั้นของเฟรมบางส่วนหรือทั้งหมด โดยการประมวลผลข้อมูลเรขาคณิตสำหรับแต่ละโหมดแสดงภาพที่ต้องใช้ข้อมูลดังกล่าว
ตำแหน่งของโหมดแสดงภาพระดับรูทคือ 0,0 และมิติข้อมูลคือวิวพอร์ต ซึ่งเป็นส่วนที่มองเห็นได้ของหน้าต่างเบราว์เซอร์
โหมดแสดงภาพทั้งหมดมี "การจัดวาง" หรือ "จัดเรียงใหม่" โดยโหมดแสดงภาพแต่ละรายการจะเรียกเมธอดการจัดวางของรายการย่อยที่ต้องการการจัดวาง
ระบบบิตสกปรก
เบราว์เซอร์จะใช้ "บิตสกปรก" เพื่อจะได้ไม่ต้องทำการจัดวางแบบเต็มสำหรับการเปลี่ยนแปลงเล็กๆ น้อยๆ ทุกครั้ง ระบบ โหมดแสดงภาพที่มีการเปลี่ยนแปลงหรือเพิ่มจะทำเครื่องหมายตัวเองและองค์ประกอบย่อยว่า "สกปรก": จำเป็นต้องมีเลย์เอาต์
มี 2 สถานะ คือ "สกปรก" และ "เด็กสกปรก" ซึ่งหมายความว่าแม้ว่าโหมดแสดงภาพอาจใช้งานได้ แต่ก็มีลูกอย่างน้อย 1 คนที่ต้องมีเลย์เอาต์
เลย์เอาต์ส่วนกลางและส่วนเพิ่ม
เลย์เอาต์สามารถทริกเกอร์ในแผนผังการแสดงภาพทั้งหมด ซึ่งก็คือ "ส่วนกลาง" เลย์เอาต์ ซึ่งอาจเกิดจากสาเหตุต่อไปนี้
- การเปลี่ยนแปลงรูปแบบส่วนกลางที่มีผลกับโหมดแสดงภาพทั้งหมด เช่น การเปลี่ยนขนาดแบบอักษร
- เนื่องจากมีการปรับขนาดหน้าจอ
คุณสามารถเพิ่มเลย์เอาต์ได้เฉพาะโหมดแสดงภาพสกปรกเท่านั้น (ซึ่งอาจทำให้เกิดความเสียหายบางอย่างซึ่งจะต้องมีเลย์เอาต์เพิ่มเติม)
ระบบจะทริกเกอร์เลย์เอาต์ส่วนเพิ่ม (ไม่พร้อมกัน) เมื่อโหมดแสดงภาพสกปรก ตัวอย่างเช่น เมื่อมีการเพิ่มโหมดแสดงภาพใหม่ต่อท้ายทรีการแสดงผลหลังจากเนื้อหาส่วนเกินมาจากเครือข่ายและได้รับการเพิ่มลงในแผนผัง DOM
![เลย์เอาต์ส่วนเพิ่ม](https://web.dev/static/articles/howbrowserswork/image/incremental-layout-da3da0a148135.png?authuser=6&hl=th)
เลย์เอาต์แบบอะซิงโครนัสและซิงโครนัส
เลย์เอาต์ส่วนเพิ่มจะทำงานไม่พร้อมกัน Firefox จัดคิว "คำสั่งซ้ำ" สำหรับเลย์เอาต์ส่วนเพิ่มและเครื่องจัดตารางเวลาจะทริกเกอร์การดำเนินการแบบกลุ่มของคำสั่งเหล่านี้ นอกจากนี้ WebKit ยังมีตัวจับเวลาดำเนินการกับเลย์เอาต์ที่เพิ่มขึ้น กล่าวคือต้นไม้ถูกข้ามผ่านและ "สกปรก" โหมดแสดงภาพมีการจัดวางไว้
สคริปต์ที่ขอข้อมูลรูปแบบ เช่น "offsetHeight" จะทริกเกอร์เลย์เอาต์ส่วนเพิ่มได้พร้อมกัน
โดยปกติแล้วเลย์เอาต์ส่วนกลางจะทริกเกอร์พร้อมกัน
บางครั้งเลย์เอาต์จะถูกทริกเกอร์เป็น Callback หลังจากเลย์เอาต์เริ่มต้น เนื่องจากแอตทริบิวต์บางอย่าง เช่น ตำแหน่งการเลื่อนมีการเปลี่ยนแปลง
การเพิ่มประสิทธิภาพ
เมื่อมีการทริกเกอร์เลย์เอาต์เมื่อมีการ "ปรับขนาด" หรือการเปลี่ยนแปลงตำแหน่งโหมดแสดงภาพ(ไม่ใช่ขนาด) ขนาดในการแสดงผลจะนำมาจากแคชและไม่ถูกคำนวณใหม่...
ในบางกรณี จะมีเพียงแผนผังย่อยเท่านั้นที่ได้รับการแก้ไขและเลย์เอาต์ไม่ได้เริ่มจากรูท ซึ่งสามารถเกิดขึ้นได้ในกรณีที่การเปลี่ยนแปลงเกิดขึ้นเฉพาะที่และไม่ส่งผลกระทบต่อสภาพแวดล้อม เช่น ข้อความที่แทรกลงในช่องข้อความ (มิเช่นนั้นการกดแป้นพิมพ์ทุกครั้งจะทริกเกอร์เลย์เอาต์ที่เริ่มจากรูท)
ขั้นตอนการจัดวาง
เลย์เอาต์มักจะมีรูปแบบต่อไปนี้
- ตัวแสดงผลระดับบนสุดจะกำหนดความกว้างของตัวเอง
- ผู้ปกครองข้ามเด็กไปและ:
- วางโหมดแสดงภาพย่อย (ตั้งค่า x และ y)
- เรียกใช้เลย์เอาต์ย่อยหากจำเป็น โดยแท็กสกปรกหรือเราอยู่ในเลย์เอาต์ส่วนกลาง หรือด้วยเหตุผลอื่น ซึ่งจะคำนวณความสูงของเด็ก
- เครือข่ายหลักจะใช้ความสูงสะสมของเด็ก รวมถึงความสูงของระยะขอบและระยะห่างจากขอบเพื่อกำหนดความสูงเอง - ระดับบนสุดของตัวแสดงผลหลักจะใช้ค่านี้
- ตั้งค่าดอกสว่านให้เป็นเท็จ
Firefox ใช้ "สถานะ" object(nsHTMLReflowState) เป็นพารามิเตอร์ของเลย์เอาต์ (เรียกว่า "reflow") ส่วนรัฐอื่นๆ ก็มีความกว้างผู้ปกครอง
เอาต์พุตของเลย์เอาต์ของ Firefox คือ "เมตริก" object(nsHTMLReflowMetrics) จะมีความสูงที่คำนวณของโหมดแสดงภาพ
การคำนวณความกว้าง
ความกว้างของตัวแสดงผลจะคำนวณโดยใช้ความกว้างของบล็อกคอนเทนเนอร์ ซึ่งเป็นรูปแบบ "ความกว้าง" ของการแสดงผล รวมถึงขอบและระยะขอบ
ตัวอย่างเช่น ความกว้างของ div ต่อไปนี้
<div style="width: 30%"/>
จะคำนวณโดย WebKit ดังต่อไปนี้(คลาส RenderBox ของเมธอด calcWidth):
- ความกว้างของคอนเทนเนอร์คือค่าสูงสุดของคอนเทนเนอร์ที่มีความกว้างและ 0 ค่าที่มีความกว้างในกรณีนี้คือ contentWidth ซึ่งมีวิธีคำนวณดังนี้
clientWidth() - paddingLeft() - paddingRight()
clientwidth และ clientHeight แสดงภายในของออบเจ็กต์ ไม่รวมเส้นขอบและแถบเลื่อน
ความกว้างขององค์ประกอบคือ "width" style [รูปแบบ] ซึ่งจะคำนวณเป็นค่าสัมบูรณ์ด้วยการคำนวณเปอร์เซ็นต์ของความกว้างคอนเทนเนอร์
ตอนนี้มีการเพิ่มเส้นขอบแนวนอนและระยะห่างจากขอบ
ก่อนหน้านี้นี่คือการคำนวณ "ความกว้างที่ต้องการ" ตอนนี้จะมีการคำนวณความกว้างต่ำสุดและสูงสุด
หากความกว้างที่ต้องการมากกว่าความกว้างสูงสุด ระบบจะใช้ความกว้างสูงสุด หากน้อยกว่าความกว้างขั้นต่ำ (หน่วยที่แตกได้ซึ่งเล็กที่สุด) ระบบจะใช้ความกว้างขั้นต่ำ
ระบบจะแคชค่าไว้ในกรณีที่ต้องใช้เลย์เอาต์ แต่ความกว้างจะไม่เปลี่ยนแปลง
ตัวแบ่งบรรทัด
เมื่อโหมดแสดงภาพที่อยู่ตรงกลางของเค้าโครงระบุว่าจำเป็นต้องแยกการแสดงผล โหมดแสดงภาพจะหยุดและเผยแพร่ไปยังระดับบนสุดของเค้าโครงและจำเป็นต้องเสียหาย ผู้เผยแพร่โฆษณาหลักสร้างตัวแสดงผลและเลย์เอาต์การเรียกใช้เพิ่มเติม
การวาดภาพ
ในขั้นการวาดภาพ ระบบจะข้ามต้นไม้แสดงผลและ "paint()" ของโหมดแสดงภาพ เพื่อแสดงเนื้อหาบนหน้าจอ ภาพวาดใช้คอมโพเนนต์โครงสร้างพื้นฐาน UI
ทั่วโลกและที่เพิ่มขึ้นเรื่อยๆ
การวาดภาพยังสามารถทำได้ทั่วโลก เช่น การวาดภาพต้นไม้ทั้งต้น หรือการเพิ่มขึ้นทีละส่วนเช่นเดียวกับเลย์เอาต์ ในการวาดภาพส่วนเพิ่ม โหมดแสดงภาพบางส่วนจะเปลี่ยนไปในลักษณะที่ไม่ส่งผลกระทบต่อต้นไม้ทั้งต้น ตัวแสดงผลที่มีการเปลี่ยนแปลงจะทำให้รูปสี่เหลี่ยมผืนผ้าบนหน้าจอใช้งานไม่ได้ ซึ่งทำให้ระบบปฏิบัติการเห็นว่าเป็น "พื้นที่สกปรก" และสร้าง "สี" กิจกรรม ระบบปฏิบัติการนี้ทำงานอย่างชาญฉลาดและรวมหลายภูมิภาคไว้ในแพลตฟอร์มเดียว ใน Chrome มีความซับซ้อนมากกว่าเนื่องจากโหมดแสดงภาพอยู่ในกระบวนการที่แตกต่างจากกระบวนการหลัก Chrome จะจําลองลักษณะการทำงานของระบบปฏิบัติการได้ไม่มาก งานนำเสนอจะฟังเหตุการณ์เหล่านี้และมอบสิทธิ์ข้อความไปยังรูทของการแสดงผล ต้นไม้จะข้ามผ่านจนกระทั่งถึงโหมดแสดงภาพที่เกี่ยวข้อง โดยจะทาสีตัวเองอีกครั้ง (และโดยทั่วไปจะเป็นองค์ประกอบย่อย)
ลำดับภาพวาด
CSS2 กำหนดลำดับของกระบวนการวาดภาพ อันที่จริงแล้วนี่เป็นลำดับที่องค์ประกอบเรียงซ้อนกันในบริบทที่ซ้อน ลำดับนี้ส่งผลต่อภาพวาดเนื่องจากกลุ่มจะลงสีจากด้านหลังไปด้านหน้า ลำดับการเรียงซ้อนของตัวแสดงผลบล็อกคือ
- สีพื้นหลัง
- ภาพพื้นหลัง
- border
- เด็ก
- outline
รายการที่แสดงของ Firefox
Firefox ไปที่แผนผังแสดงผลและสร้างรายการแสดงผลสำหรับรูปสี่เหลี่ยมผืนผ้าที่ระบายสี โดยมีโหมดแสดงภาพที่เกี่ยวข้องกับสี่เหลี่ยมผืนผ้าโดยเรียงลำดับการวาดภาพที่ถูกต้อง (พื้นหลังของโหมดแสดงภาพ จากนั้นเส้นขอบ ฯลฯ)
วิธีนี้ต้องข้ามผ่านต้นไม้เพียงครั้งเดียวเพื่อวาดรูปใหม่ แทนที่จะต้องหลายครั้ง เช่น วาดภาพพื้นหลังทั้งหมด รูปภาพทั้งหมด และเส้นขอบทั้งหมด ฯลฯ
Firefox เพิ่มประสิทธิภาพให้กับกระบวนการนี้โดยไม่เพิ่มองค์ประกอบที่จะซ่อนอยู่ เช่น องค์ประกอบที่อยู่ด้านล่างองค์ประกอบที่ทึบแสงอื่นๆ
พื้นที่เก็บข้อมูลสี่เหลี่ยมผืนผ้าของ WebKit
ก่อนการทาสีใหม่ WebKit จะบันทึกสี่เหลี่ยมผืนผ้าเก่าเป็นบิตแมป จากนั้นจะวาดเฉพาะเดลต้าระหว่างสี่เหลี่ยมผืนผ้าใหม่และเก่า
การเปลี่ยนแปลงแบบไดนามิก
เบราว์เซอร์จะพยายามดำเนินการน้อยที่สุดเท่าที่จะเป็นไปได้เพื่อตอบสนองต่อการเปลี่ยนแปลง ดังนั้นการเปลี่ยนแปลงสีขององค์ประกอบจะทำให้องค์ประกอบนั้นแสดงผลใหม่เท่านั้น การเปลี่ยนแปลงตำแหน่งองค์ประกอบจะทําให้เกิดเลย์เอาต์และการทาสีองค์ประกอบใหม่ รวมถึงองค์ประกอบย่อยและองค์ประกอบข้างเคียง การเพิ่มโหนด DOM จะทำให้เกิดเลย์เอาต์และการทาสีโหนดใหม่ การเปลี่ยนแปลงที่สำคัญ เช่น การเพิ่มขนาดแบบอักษรของ "html" จะทำให้แคช รีเลย์ และการระบายสีใหม่ในโครงสร้างต้นไม้ทั้งหมดเป็นโมฆะ
เทรดของเครื่องมือการแสดงผล
เครื่องมือการแสดงผลเป็นแบบเธรดเดี่ยว เกือบทุกอย่าง ยกเว้นการดำเนินการของเครือข่าย จะเกิดขึ้นในเทรดเดียว ใน Firefox และ Safari นี่คือเทรดหลักของเบราว์เซอร์ ใน Chrome เทรดหลักของกระบวนการแท็บ
การดำเนินการของเครือข่ายอาจทำโดยเทรดพร้อมกันหลายเทรด มีการจำกัดจำนวนการเชื่อมต่อแบบขนาน (โดยปกติ 2-6 การเชื่อมต่อ)
การวนซ้ำเหตุการณ์
โดยเทรดหลักของเบราว์เซอร์จะเป็นลูปเหตุการณ์ ซึ่งเป็นการวนซ้ำไปเรื่อยๆ ที่ทําให้กระบวนการทำงานต่อไป โมเดลจะรอเหตุการณ์ (เช่น เลย์เอาต์และการลงสีเหตุการณ์) แล้วประมวลผลเหตุการณ์ นี่คือโค้ดของ Firefox สำหรับการวนรอบเหตุการณ์หลัก:
while (!mExiting)
NS_ProcessNextEvent(thread);
โมเดลภาพ CSS2
ผืนผ้าใบ
ตามข้อกำหนด CSS2 คำว่า Canvas อธิบาย "พื้นที่ที่แสดงผลโครงสร้างการจัดรูปแบบ" ซึ่งเป็นที่ที่เบราว์เซอร์วาดภาพเนื้อหา
ผืนผ้าใบมีขนาดไม่จำกัดในแต่ละมิติของพื้นที่ทำงาน แต่เบราว์เซอร์จะเลือกความกว้างเริ่มต้นตามขนาดของวิวพอร์ต
จากข้อมูลใน www.w3.org/TR/CSS2/zindex.html ผืนผ้าใบจะโปร่งใสหากอยู่ภายในพื้นที่อื่น และให้สีที่เบราว์เซอร์กำหนดหากไม่ใช่
โมเดล CSS Box
รูปแบบกล่อง CSS อธิบายช่องสี่เหลี่ยมผืนผ้าที่สร้างขึ้นสำหรับองค์ประกอบในแผนผังเอกสารและจัดวางตามโมเดลการจัดรูปแบบภาพ
แต่ละช่องจะมีพื้นที่เนื้อหา (เช่น ข้อความ รูปภาพ ฯลฯ) และสามารถเลือกล้อมรอบระยะห่างจากขอบ ขอบ และพื้นที่ขอบ
![รูปแบบกล่อง CSS2](https://web.dev/static/articles/howbrowserswork/image/css2-box-model-9c2ab852d1fb4.jpg?authuser=6&hl=th)
แต่ละโหนดจะสร้างช่องเช่นนี้ขึ้นมา 0...n กล่อง
องค์ประกอบทั้งหมดมี "display" ซึ่งกำหนดประเภทของช่องที่จะสร้างขึ้น
ตัวอย่าง
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
แผนการกำหนดตำแหน่ง
โดยมี 3 แผน ดังนี้
- ปกติ: จัดตำแหน่งวัตถุตามตำแหน่งในเอกสาร ซึ่งหมายความว่าตำแหน่งในแผนผังการแสดงผลจะเหมือนกับตำแหน่งในแผนผัง DOM และจัดวางตามประเภทช่องและขนาด
- ลอย: เริ่มจากการวางวัตถุเหมือนกับโฟลว์ปกติ แล้วเลื่อนไปทางซ้ายหรือขวาให้ได้มากที่สุด
- สัมบูรณ์: วางออบเจ็กต์ในแผนผังแสดงผลในตำแหน่งอื่นที่ต่างจากในแผนผัง DOM
รูปแบบการกำหนดตำแหน่งจะกำหนดโดย "ตำแหน่ง" และ "Float"
- สาเหตุแบบคงที่และสาเหตุที่เกี่ยวข้องซึ่งไหลลื่นตามปกติ
- สาเหตุของตำแหน่งแบบสัมบูรณ์และคงที่
ในการวางตำแหน่งคงที่ ไม่มีการกำหนดตำแหน่งใดๆ และจะใช้ตำแหน่งเริ่มต้น ในรูปแบบอื่น ผู้เขียนจะระบุตำแหน่ง: บน ล่าง ซ้าย ขวา
วิธีการวางกล่องจะกำหนดสิ่งต่อไปนี้
- ประเภทกล่อง
- ขนาดกล่อง
- แผนการกำหนดตำแหน่ง
- ข้อมูลภายนอก เช่น ขนาดรูปภาพและขนาดหน้าจอ
ประเภทกล่อง
กล่องบล็อก: สร้างบล็อก - มีสี่เหลี่ยมผืนผ้าของตัวเองในหน้าต่างเบราว์เซอร์
![กล่องบล็อก](https://web.dev/static/articles/howbrowserswork/image/block-box-b5c0bff4a44d2.png?authuser=6&hl=th)
กล่องในบรรทัด: ไม่มีบล็อกของตัวเอง แต่อยู่ภายในบล็อกที่มีแท็กอยู่
![ช่องที่แทรกในบรรทัด](https://web.dev/static/articles/howbrowserswork/image/inline-boxes-a9ff03002e7e2.png?authuser=6&hl=th)
บล็อกจะได้รับการจัดรูปแบบในแนวตั้งทีละบล็อก บรรทัดแทรกจะมีการจัดรูปแบบในแนวนอน
![การจัดรูปแบบบล็อกและแทรกในบรรทัด](https://web.dev/static/articles/howbrowserswork/image/block-inline-formatting-a8450c63cf457.png?authuser=6&hl=th)
ช่องในบรรทัดจะวางอยู่ในบรรทัดหรือ "ช่องบรรทัด" แต่ละเส้นจะสูงเท่ากับกล่องที่สูงที่สุดเป็นอย่างน้อย แต่ก็อาจสูงขึ้นได้เมื่อแต่ละเส้นอยู่ในแนว "เส้นฐาน" - หมายถึงส่วนด้านล่างขององค์ประกอบอยู่ในแนวเดียวกันที่จุดหนึ่งของอีกช่องหนึ่ง จากนั้นอยู่ด้านล่าง หากความกว้างของคอนเทนเนอร์ไม่เพียงพอ ระบบจะใส่ในบรรทัดหลายบรรทัด ซึ่งโดยปกติคือสิ่งที่เกิดขึ้นในย่อหน้าหนึ่ง
![บรรทัด](https://web.dev/static/articles/howbrowserswork/image/lines-68c55a378a7ff.png?authuser=6&hl=th)
การอธิบายหัวข้อต่างๆ แก่ลูกค้า
ญาติ
การวางตำแหน่งแบบสัมพัทธ์ - วางตำแหน่งตามปกติแล้วย้ายไปตามเดลต้าที่ต้องการ
![การวางตำแหน่งแบบสัมพัทธ์](https://web.dev/static/articles/howbrowserswork/image/relative-positioning-fceed0670b8c5.png?authuser=6&hl=th)
แบบลอย
กล่องแบบลอยถูกเลื่อนไปทางซ้ายหรือขวาของเส้น จุดสนใจที่น่าสนใจคือกล่องอื่นๆ จะล้อมรอบไปด้วย HTML มีลักษณะดังนี้
<p>
<img style="float: right" src="images/image.gif" width="100" height="100">
Lorem ipsum dolor sit amet, consectetuer...
</p>
จะมีหน้าตาดังนี้
![ลอย](https://web.dev/static/articles/howbrowserswork/image/float-dd9b790210284.png?authuser=6&hl=th)
ค่าสัมบูรณ์และคงที่
เลย์เอาต์จะได้รับการกำหนดไว้ทุกประการโดยไม่คำนึงถึงขั้นตอนปกติ องค์ประกอบไม่ได้มีส่วนร่วมในขั้นตอนปกติ มิติข้อมูลจะสัมพันธ์กับคอนเทนเนอร์ ในช่องคงที่ คอนเทนเนอร์คือวิวพอร์ต
![การจัดตำแหน่งคงที่](https://web.dev/static/articles/howbrowserswork/image/fixed-positioning-881933fcb1a2f.png?authuser=6&hl=th)
การนำเสนอแบบแบ่งชั้น
ค่านี้ระบุโดยพร็อพเพอร์ตี้ CSS ดัชนี z โดยแสดงมิติที่ 3 ของช่อง นั่นคือ ตำแหน่งตาม "แกน z"
ช่องต่างๆ จะแบ่งออกเป็นกลุ่ม (เรียกว่าบริบทแบบเรียงซ้อน) ในแต่ละกอง องค์ประกอบด้านหลังจะลงสีก่อน และองค์ประกอบข้างหน้าจะอยู่ด้านบน ซึ่งอยู่ใกล้กับผู้ใช้มากกว่า ในกรณีที่ซ้อนทับกัน องค์ประกอบที่สำคัญที่สุดจะซ่อนองค์ประกอบเดิม
กองซ้อนจะเรียงลำดับตามพร็อพเพอร์ตี้ดัชนี z กล่องที่มี "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>
ผลลัพธ์ที่ได้คือ
![การจัดตำแหน่งคงที่](https://web.dev/static/articles/howbrowserswork/image/fixed-positioning-84a46b366dc9a.png?authuser=6&hl=th)
แม้ว่า div สีแดงอยู่ก่อนสีเขียวในมาร์กอัป และทาสีก่อนในโฟลว์ปกติ แต่พร็อพเพอร์ตี้ดัชนี z จะสูงกว่า จึงอยู่ข้างหน้าในสแต็กที่ช่องรากอยู่มากกว่า
แหล่งข้อมูล
สถาปัตยกรรมของเบราว์เซอร์
- Grosskurth, Alan สถาปัตยกรรมอ้างอิงสำหรับเว็บเบราว์เซอร์ (pdf)
- Gupta, วิเนศ วิธีการทำงานของเบราว์เซอร์ - ตอนที่ 1 - สถาปัตยกรรม
การแยกวิเคราะห์
- Aho, Sethi, Ullman, Compilers: Principles, Techniques, and Tools (หรือที่เรียก "หนังสือ Dragon"), Addison-Wesley, 1986
- Rick Jelliffe The Bold and the Beautiful: ตัวอย่างฉบับร่าง 2 แบบสำหรับ HTML 5
Firefox
- L. David Baron, HTML และ CSS ที่เร็วขึ้น: Layout Engine Internals สำหรับนักพัฒนาเว็บ
- L. David Baron, ผ่าน HTML และ CSS ที่เร็วขึ้น: Layout Engine Internals สำหรับนักพัฒนาเว็บ (วิดีโอการพูดคุยเกี่ยวกับเทคโนโลยีของ Google)
- L. David Baron, Layout Engine ของ Mozilla
- L. David Baron, เอกสารระบบ Mozilla Style System
- Chris Waterson หมายเหตุเกี่ยวกับการจัดเรียง HTML
- Chris Waterson ภาพรวม Gecko
- Alexander Larsson, วงจรชีวิตของคำขอ HTTP HTML
WebKit
- David Hyatt, การใช้งาน CSS(ตอนที่ 1)
- David Hyatt, ภาพรวมของ WebCore
- David Hyatt, การแสดงภาพ WebCore
- David Hyatt, ปัญหา FOUC
ข้อกำหนดเกี่ยวกับ W3C
วิธีการสร้างเบราว์เซอร์
คำแปล
หน้าเว็บนี้ได้รับการแปลเป็นภาษาญี่ปุ่น 2 ครั้ง:
- วิธีการทำงานของเบราว์เซอร์ - เบื้องหลังเว็บเบราว์เซอร์สมัยใหม่ (ja) โดย @kosei
- ブラウザってどうやって動いてるの?(モダンWEBブラウザシーンの裏側 โดย @ikeike443 และ @kiyoto01
คุณสามารถดูคำแปลที่โฮสต์ภายนอกของ เกาหลีและ ตุรกี
ขอบคุณทุกคน