Cara kerja browser

Di balik layar browser web modern

Pengantar

Panduan lengkap tentang operasi internal WebKit dan Gecko ini adalah hasil dari banyak riset yang dilakukan oleh developer Israel, Tali Garsiel. Selama beberapa tahun, dia meninjau semua data yang dipublikasikan tentang internal browser dan menghabiskan banyak waktu membaca kode sumber browser web. Ia menulis:

Sebagai developer web, mempelajari bagian dalam operasi browser akan membantu Anda membuat keputusan yang lebih baik dan mengetahui alasan di balik praktik terbaik pengembangan. Meskipun ini adalah dokumen yang cukup panjang, sebaiknya luangkan waktu untuk mempelajarinya. Anda akan senang Anda melakukannya.

Paul Irish, Chrome Developer Relations

Pengantar

{i>Browser<i} internet adalah perangkat lunak yang paling banyak digunakan. Dalam penjelasan ini, saya menjelaskan bagaimana mereka bekerja di belakang layar. Kita akan melihat apa yang terjadi saat Anda mengetik google.com di kolom URL sampai Anda melihat halaman Google di layar browser.

Browser yang akan kita bahas

Ada lima browser utama yang digunakan di desktop saat ini: Chrome, Internet Explorer, Firefox, Safari, dan Opera. Pada ponsel, browser utama adalah Browser Android, iPhone, Opera Mini dan Opera Mobile, UC Browser, browser Nokia S40/S60 dan Chrome, yang semuanya, kecuali untuk browser Opera, didasarkan pada WebKit. Saya akan memberikan contoh dari browser open source Firefox dan Chrome, serta Safari (yang sebagian merupakan open source). Menurut statistik StatCounter (per Juni 2013), Chrome, Firefox dan Safari menyumbang sekitar 71% dari penggunaan browser desktop global. Di perangkat seluler, Browser Android, iPhone, dan Chrome membentuk sekitar 54% penggunaan.

Fungsi utama browser

Fungsi utama browser adalah menyajikan sumber daya web yang Anda pilih, dengan memintanya dari server dan menampilkannya di jendela browser. Resource ini biasanya berupa dokumen HTML, tetapi juga dapat berupa PDF, gambar, atau jenis konten lainnya. Lokasi resource ditentukan oleh pengguna menggunakan URI (Uniform Resource Identifier).

Cara browser menafsirkan dan menampilkan file HTML ditentukan dalam spesifikasi HTML dan CSS. Spesifikasi ini dikelola oleh organisasi W3C (World Wide Web Consortium), yang merupakan organisasi standar untuk web. Selama bertahun-tahun, browser hanya sesuai dengan sebagian spesifikasi dan mengembangkan ekstensinya sendiri. Hal tersebut menyebabkan masalah kompatibilitas yang serius bagi penulis web. Saat ini sebagian besar {i>browser<i} kurang lebih sesuai dengan spesifikasi.

Antarmuka pengguna browser memiliki banyak kesamaan satu sama lain. Di antara elemen antarmuka pengguna yang umum adalah:

  1. Kolom URL untuk menyisipkan URI
  2. Tombol kembali dan maju
  3. Opsi bookmark
  4. Tombol muat ulang dan berhenti untuk memuat ulang atau menghentikan pemuatan dokumen saat ini
  5. Tombol Beranda yang mengarahkan Anda ke halaman beranda

Anehnya, antarmuka pengguna browser tidak ditentukan dalam spesifikasi formal apa pun, melainkan hanya berasal dari praktik terbaik yang dibentuk selama bertahun-tahun dan oleh browser yang meniru satu sama lain. Spesifikasi HTML5 tidak mendefinisikan elemen UI yang harus dimiliki browser, tetapi mencantumkan beberapa elemen umum. Di antaranya adalah kolom URL, status bar, dan toolbar. Tentu saja ada fitur yang unik di browser tertentu, seperti pengelola download Firefox.

Infrastruktur tingkat tinggi

Komponen utama browser adalah:

  1. Antarmuka pengguna: ini mencakup kolom URL, tombol kembali/maju, menu bookmark, dll. Setiap bagian browser ditampilkan, kecuali jendela tempat Anda melihat halaman yang diminta.
  2. Mesin browser: melakukan tindakan marshal antara UI dan mesin rendering.
  3. Mesin rendering: bertanggung jawab untuk menampilkan konten yang diminta. Misalnya, jika konten yang diminta adalah HTML, mesin rendering akan mengurai HTML dan CSS, serta menampilkan konten yang diuraikan di layar.
  4. Jaringan: untuk panggilan jaringan seperti permintaan HTTP, menggunakan implementasi yang berbeda untuk platform yang berbeda di balik antarmuka yang tidak bergantung pada platform.
  5. UI backend: digunakan untuk menggambar widget dasar seperti combo box dan jendela. Backend ini mengekspos antarmuka generik yang tidak spesifik untuk platform. Di bawahnya digunakan metode antarmuka pengguna sistem operasi.
  6. Penafsir JavaScript. Digunakan untuk mengurai dan mengeksekusi kode JavaScript.
  7. Penyimpanan Data. Ini adalah layer persistensi. Browser mungkin perlu menyimpan semua jenis data secara lokal, seperti cookie. Browser juga mendukung mekanisme penyimpanan seperti localStorage, IndexedDB, WebSQL, dan FileSystem.
Komponen browser
Gambar 1: Komponen browser

Perlu diperhatikan bahwa browser seperti Chrome menjalankan beberapa instance mesin rendering: satu untuk setiap tab. Setiap tab berjalan dalam proses terpisah.

Mesin rendering

Tanggung jawab mesin rendering adalah... Rendering, yaitu menampilkan konten yang diminta pada layar browser.

Secara default, mesin rendering dapat menampilkan dokumen serta gambar HTML dan XML. Alat ini dapat menampilkan jenis data lain melalui plugin atau ekstensi; misalnya, menampilkan dokumen PDF menggunakan plugin penampil PDF. Namun, dalam bab ini kita akan berfokus pada kasus penggunaan utama: menampilkan HTML dan gambar yang diformat menggunakan CSS.

Browser yang berbeda menggunakan mesin render yang berbeda: Internet Explorer menggunakan Trident, Firefox menggunakan Gecko, dan Safari menggunakan WebKit. Chrome dan Opera (dari versi 15) menggunakan Blink, cabang WebKit.

WebKit adalah mesin rendering open source yang dimulai sebagai mesin untuk platform Linux dan dimodifikasi oleh Apple untuk mendukung Mac dan Windows.

Alur utama

Mesin rendering akan mulai mendapatkan konten dokumen yang diminta dari lapisan jaringan. Ini biasanya akan dilakukan dalam potongan 8 kB.

Setelah itu, ini adalah alur dasar mesin rendering:

Alur dasar mesin rendering
Gambar 2: Alur dasar mesin rendering

Mesin rendering akan mulai menguraikan dokumen HTML dan mengonversi elemen menjadi node DOM pada hierarki yang disebut "hierarki konten". Mesin akan mengurai data gaya, baik dalam file CSS eksternal maupun dalam elemen gaya. Informasi gaya visual bersama dengan petunjuk visual dalam HTML akan digunakan untuk membuat hierarki lainnya: hierarki render.

Pohon render berisi persegi panjang dengan atribut visual seperti warna dan dimensi. Persegi panjang berada dalam urutan yang benar untuk ditampilkan di layar.

Setelah dibuat, pohon render akan melewati proses "tata letak". Ini berarti memberi setiap node koordinat yang tepat di tempat munculnya di layar. Tahap selanjutnya adalah penggambaran - pohon render akan dilalui dan setiap node akan digambar menggunakan lapisan backend UI.

Penting untuk dipahami bahwa ini adalah proses bertahap. Untuk pengalaman pengguna yang lebih baik, mesin rendering akan mencoba menampilkan konten di layar sesegera mungkin. Fungsi ini tidak akan menunggu hingga semua HTML diuraikan sebelum mulai membangun dan menata letak hierarki render. Sebagian konten akan diuraikan dan ditampilkan, sementara proses berlanjut dengan konten lainnya yang terus berasal dari jaringan.

Contoh alur utama

Alur utama WebKit.
Gambar 3: Alur utama WebKit
Alur utama mesin rendering Gecko Mozilla.
Gambar 4: Alur utama mesin rendering Gecko Mozilla

Dari gambar 3 dan 4 Anda dapat melihat bahwa meskipun WebKit dan Gecko menggunakan terminologi yang sedikit berbeda, alurnya pada dasarnya sama.

Gecko menyebut hierarki elemen yang diformat secara visual sebagai "Hierarki bingkai". Setiap elemen adalah sebuah {i>frame<i}. WebKit menggunakan istilah "Render Tree" dan terdiri dari "Render Objects". WebKit menggunakan istilah "layout" untuk penempatan elemen, sementara Gecko menyebutnya sebagai "Reflow". "Lampiran" adalah istilah WebKit untuk menghubungkan simpul DOM dan informasi visual untuk membuat pohon render. Perbedaan kecil non-semantik adalah bahwa Gecko memiliki lapisan ekstra antara HTML dan hierarki DOM. Elemen ini disebut "content sink" dan merupakan factory untuk membuat elemen DOM. Kami akan membahas setiap bagian dari alurnya:

Penguraian - umum

Karena penguraian adalah proses yang sangat signifikan dalam mesin rendering, kita akan membahasnya sedikit lebih dalam. Mari kita mulai dengan pengenalan singkat tentang {i>parsing<i}.

Mengurai dokumen berarti menerjemahkannya ke struktur yang dapat digunakan kode. Hasil penguraian biasanya berupa pohon node yang mewakili struktur dokumen. Ini disebut pohon penguraian atau pohon sintaks.

Misalnya, mengurai ekspresi 2 + 3 - 1 dapat menampilkan hierarki ini:

Node hierarki ekspresi matematika.
Gambar 5: node hierarki ekspresi matematika

Tata bahasa

Penguraian didasarkan pada aturan sintaksis yang dipatuhi dokumen: bahasa atau format penulisannya. Setiap format yang dapat Anda urai harus memiliki tata bahasa deterministik yang terdiri dari kosakata dan aturan sintaksis. Ini disebut tata bahasa bebas konteks. Bahasa manusia bukan bahasa seperti itu dan, oleh karena itu, tidak dapat diurai dengan teknik penguraian konvensional.

Parser - Kombinasi Lexer

Penguraian dapat dipisahkan menjadi dua subproses: analisis leksikal dan analisis sintaks.

Analisis leksik adalah proses memecah input menjadi token. Token adalah kosakata bahasa: kumpulan elemen penyusun yang valid. Dalam bahasa manusia, ini akan terdiri dari semua kata yang muncul dalam kamus untuk bahasa itu.

Analisis sintaks adalah penerapan aturan sintaks bahasa.

Parser biasanya membagi pekerjaan antara dua komponen: lexer (terkadang disebut tokenizer) yang bertanggung jawab untuk memecah input menjadi token yang valid, dan parser yang bertanggung jawab untuk menyusun hierarki penguraian dengan menganalisis struktur dokumen sesuai dengan aturan sintaksis bahasa.

Lexer tahu cara menghapus karakter yang tidak relevan seperti spasi putih dan jeda baris.

Dari dokumen sumber hingga mengurai hierarki
Gambar 6: dari dokumen sumber untuk mengurai hierarki

Proses penguraian bersifat iteratif. Parser biasanya akan meminta token baru ke lexer dan mencoba mencocokkan token tersebut dengan salah satu aturan sintaksis. Jika aturan cocok, node yang sesuai dengan token akan ditambahkan ke pohon penguraian dan parser akan meminta token lain.

Jika tidak ada aturan yang cocok, parser akan menyimpan token secara internal, dan terus meminta token hingga aturan yang cocok dengan semua token yang disimpan secara internal ditemukan. Jika tidak ada aturan yang ditemukan, parser akan memunculkan pengecualian. Hal ini berarti dokumen tidak valid dan berisi error sintaksis.

Terjemahan

Dalam banyak kasus, pohon penguraian bukanlah produk akhir. Penguraian sering digunakan dalam terjemahan: mengubah dokumen input ke format lain. Contohnya adalah kompilasi. Compiler yang mengompilasi kode sumber ke dalam kode mesin akan mengurainya terlebih dahulu menjadi pohon penguraian, lalu menerjemahkan pohon tersebut menjadi dokumen kode mesin.

Alur kompilasi
Gambar 7: alur kompilasi

Contoh penguraian

Pada gambar 5, kita membuat pohon parse dari ekspresi matematika. Mari kita coba mendefinisikan bahasa matematika sederhana dan melihat proses penguraiannya.

Sintaks:

  1. Elemen penyusun {i>syntax<i} bahasa adalah ekspresi, istilah, dan operasi.
  2. Bahasa kita dapat mencakup sejumlah ekspresi.
  3. Sebuah ekspresi didefinisikan sebagai "istilah" yang diikuti oleh "operasi" yang diikuti oleh istilah lain
  4. Sebuah operasi adalah token plus atau token minus
  5. Istilah adalah token bilangan bulat atau ekspresi

Mari kita analisis input 2 + 3 - 1.

Substring pertama yang cocok dengan aturan adalah 2: menurut aturan #5, string tersebut adalah istilah. Kecocokan kedua adalah 2 + 3: ini cocok dengan aturan ketiga: istilah yang diikuti dengan operasi yang diikuti oleh istilah lainnya. Kecocokan berikutnya hanya akan diklik di akhir input. 2 + 3 - 1 adalah ekspresi karena kita sudah tahu bahwa 2 + 3 adalah sebuah istilah, jadi kita memiliki istilah yang diikuti oleh operasi yang diikuti oleh istilah lainnya. 2 + + tidak akan cocok dengan aturan apa pun sehingga merupakan input yang tidak valid.

Definisi formal untuk kosakata dan sintaks

Kosakata biasanya dinyatakan dengan ekspresi reguler.

Misalnya, bahasa kita akan ditentukan sebagai:

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

Seperti yang Anda lihat, bilangan bulat ditentukan oleh ekspresi reguler.

Sintaksis biasanya ditentukan dalam format yang disebut BNF. Bahasa kita akan didefinisikan sebagai:

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

Kami menyatakan bahwa suatu bahasa dapat diurai oleh parser biasa jika tata bahasanya merupakan tata bahasa bebas konteks. Definisi intuitif dari tata bahasa bebas konteks adalah tata bahasa yang dapat dinyatakan sepenuhnya dalam BNF. Untuk definisi formal, lihat artikel Wikipedia tentang tata bahasa bebas konteks

Jenis parser

Ada dua jenis parser: parser top down dan parser bottom up. Penjelasan intuitif adalah bahwa parser top down memeriksa struktur tingkat tinggi sintaksis dan mencoba menemukan kecocokan aturan. Parser bottom up dimulai dengan input, lalu mengubahnya secara bertahap menjadi aturan sintaksis, dimulai dari aturan tingkat rendah hingga aturan tingkat tinggi terpenuhi.

Mari kita lihat cara kedua jenis parser akan mengurai contoh kita.

Parser top down akan dimulai dari aturan tingkat yang lebih tinggi: parser akan mengidentifikasi 2 + 3 sebagai ekspresi. Selanjutnya, kode ini akan mengidentifikasi 2 + 3 - 1 sebagai ekspresi (proses identifikasi ekspresi berkembang, cocok dengan aturan lainnya, tetapi titik awalnya adalah aturan tingkat tertinggi).

Parser bottom up akan memindai input hingga aturan cocok. Kemudian, input yang cocok akan diganti dengan aturan tersebut. Ini akan berlangsung sampai akhir input. Ekspresi yang cocok sebagian ditempatkan di stack parser.

Bertumpuk Input
2 + 3 - 1
term + 3 - 1
operasi jangka 3 - 1
ekspresi - 1
operasi ekspresi 1
ekspresi -

Jenis parser bottom up ini disebut parser shift-pengurangan, karena input digeser ke kanan (bayangkan pointer yang menunjuk terlebih dahulu pada awal input dan bergerak ke kanan) dan secara bertahap dikurangi ke aturan sintaksis.

Membuat parser secara otomatis

Ada alat yang dapat menghasilkan parser. Anda memberi mereka tata bahasa bahasa Anda - aturan kosakata dan sintaksisnya - dan mereka menghasilkan parser yang berfungsi. Membuat parser memerlukan pemahaman mendalam tentang penguraian dan tidak mudah membuat parser yang dioptimalkan secara manual sehingga generator parser dapat sangat berguna.

WebKit menggunakan dua generator parser yang terkenal: Flex untuk membuat lexer dan Bison untuk membuat parser (Anda mungkin menemukannya dengan nama Lex dan Yacc). Input fleksibel adalah file yang berisi definisi ekspresi reguler token. Input Bison adalah aturan sintaksis bahasa dalam format BNF.

Parser HTML

Tugas parser HTML adalah mengurai markup HTML menjadi pohon penguraian.

Tata bahasa HTML

Kosakata dan sintaks HTML didefinisikan dalam spesifikasi yang dibuat oleh organisasi W3C.

Seperti yang telah kita lihat dalam pengantar penguraian, {i>syntax<i} tata bahasa dapat didefinisikan secara formal menggunakan format seperti BNF.

Sayangnya semua topik parser konvensional tidak berlaku untuk HTML (saya tidak membahasnya hanya untuk bersenang-senang - topik ini akan digunakan dalam mengurai CSS dan JavaScript). HTML tidak dapat dengan mudah didefinisikan oleh tata bahasa bebas konteks yang diperlukan parser.

Ada format formal untuk mendefinisikan HTML - DTD (Document Type Definition) - tetapi ini bukan tata bahasa bebas konteks.

Ini tampak aneh pada awalnya; HTML agak mirip dengan XML. Ada banyak parser XML yang tersedia. Ada variasi XML dari HTML - XML - jadi apa perbedaan besarnya?

Perbedaannya adalah, pendekatan HTML lebih bersifat "maaf": ini memungkinkan Anda menghilangkan tag tertentu (yang kemudian ditambahkan secara implisit), atau terkadang menghilangkan tag awal atau akhir, dan seterusnya. Secara keseluruhan, ini adalah sintaksis "lunak", berbeda dengan sintaksis XML yang kaku dan berat.

Detail yang tampaknya kecil ini membuat dunia berbeda. Di satu sisi, ini adalah alasan utama mengapa HTML begitu populer: HTML memaafkan kesalahan Anda dan membuat hidup penulis web lebih mudah. Di sisi lain, penulisan tata bahasa formal menyulitkan akan menyulitkan. Jadi untuk meringkas, HTML tidak dapat diurai dengan mudah oleh parser konvensional, karena tata bahasanya tidak bebas konteks. HTML tidak dapat diuraikan oleh parser XML.

DTD HTML

Definisi HTML dalam format DTD. Format ini digunakan untuk menentukan bahasa kelompok SGML. Format ini berisi definisi untuk semua elemen yang diizinkan, atributnya, dan hierarkinya. Seperti yang telah kita lihat sebelumnya, DTD HTML tidak membentuk tata bahasa bebas konteks.

Ada beberapa variasi DTD. Mode ketat hanya mengikuti spesifikasi tetapi mode lain berisi dukungan untuk markup yang digunakan oleh browser di masa lalu. Tujuannya adalah kompatibilitas mundur dengan konten lama. DTD ketat saat ini ada di sini: www.w3.org/TR/html4/strict.dtd

DOM

Pohon output ("pohon penguraian") adalah pohon elemen DOM dan simpul atribut. DOM adalah singkatan dari Document Object Model. Ini adalah presentasi objek dari dokumen HTML dan antarmuka elemen HTML ke dunia luar seperti JavaScript.

Akar hierarki adalah objek "Document".

DOM memiliki relasi yang hampir satu-ke-satu dengan markup. Contoh:

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

Markup ini akan diterjemahkan ke dalam hierarki DOM berikut:

Hierarki DOM dari markup contoh
Gambar 8: Hierarki DOM dari markup contoh

Seperti HTML, DOM ditentukan oleh organisasi W3C. Lihat www.w3.org/DOM/DOMTR. Ini adalah spesifikasi umum untuk memanipulasi dokumen. Modul tertentu menjelaskan elemen khusus HTML. Definisi HTML dapat ditemukan di sini: www.w3.org/TR/2003/REC-DOM-Level-2-HTML-20030109/idl-definitions.html.

Maksud saya, hierarki berisi simpul DOM, artinya hierarki dibuat dari elemen yang mengimplementasikan salah satu antarmuka DOM. Browser menggunakan implementasi konkret yang memiliki atribut lain yang digunakan oleh browser secara internal.

Algoritma penguraian

Seperti yang kita lihat di bagian sebelumnya, HTML tidak dapat diurai menggunakan parser reguler dari atas ke bawah atau dari bawah ke atas.

Alasannya adalah:

  1. Sifat bahasa yang mudah memaafkan.
  2. Fakta bahwa browser memiliki toleransi kesalahan tradisional untuk mendukung kasus HTML tidak valid yang diketahui.
  3. Proses penguraian bersifat reentrant. Untuk bahasa lain, sumber tidak berubah selama penguraian, tetapi dalam HTML, kode dinamis (seperti elemen skrip yang berisi panggilan document.write()) dapat menambahkan token tambahan, sehingga proses penguraian benar-benar mengubah input.

Tidak dapat menggunakan teknik penguraian reguler, browser membuat parser kustom untuk mengurai HTML.

Algoritma penguraian dijelaskan secara mendetail oleh spesifikasi HTML5. Algoritma ini terdiri dari dua tahap: tokenisasi dan konstruksi pohon.

Tokenisasi adalah analisis leksikal, yaitu mengurai input menjadi token. Di antara token HTML adalah tag start, tag akhir, nama atribut, dan nilai atribut.

Tokenizer mengenali token, memberikannya ke konstruktor hierarki, dan menggunakan karakter berikutnya untuk mengenali token berikutnya, dan seterusnya hingga akhir input.

Alur penguraian HTML (diambil dari spesifikasi HTML5)
Gambar 9: Alur penguraian HTML (diambil dari spesifikasi HTML5)

Algoritma tokenisasi

Output algoritma adalah token HTML. Algoritma dinyatakan sebagai mesin status. Setiap status menggunakan satu atau beberapa karakter stream input dan memperbarui status berikutnya sesuai dengan karakter tersebut. Keputusan tersebut dipengaruhi oleh status tokenisasi saat ini dan status konstruksi pohon. Artinya, karakter yang sama yang digunakan akan memberikan hasil yang berbeda untuk status berikutnya yang benar, bergantung pada status saat ini. Algoritma ini terlalu rumit untuk dijelaskan sepenuhnya. Jadi, mari kita lihat contoh sederhana yang akan membantu kita memahami prinsipnya.

Contoh dasar - membuat token HTML berikut:

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

Status awalnya adalah "Data state". Saat karakter < ditemukan, status diubah menjadi "Status terbuka tag". Memakai karakter a-z akan menyebabkan pembuatan "Mulai token tag", status diubah menjadi "Status nama tag". Kita tetap dalam status ini sampai karakter > digunakan. Setiap karakter ditambahkan ke nama token baru. Dalam hal ini, token yang dibuat adalah token html.

Saat tag > tercapai, token saat ini akan dimunculkan dan status berubah kembali ke "Status data". Tag <body> akan ditangani dengan langkah-langkah yang sama. Sejauh ini, tag html dan body dimunculkan. Kita sekarang kembali ke "Data state". Memakai karakter H dari Hello world akan menyebabkan pembuatan dan pemunculan token karakter, hal ini akan berlangsung hingga < dari </body> tercapai. Kita akan memunculkan token karakter untuk setiap karakter Hello world.

Kita sekarang kembali ke "Status terbuka tag". Memakai input berikutnya / akan menyebabkan pembuatan end tag token dan pemindahan ke "Status nama tag". Sekali lagi, kita tetap dalam status ini sampai mencapai >.Kemudian, token tag baru akan dibuat dan kita kembali ke "Status data". Input </html> akan diperlakukan seperti kasus sebelumnya.

Membuat token input contoh
Gambar 10: Membuat token input contoh

Algoritma konstruksi pohon

Saat parser dibuat, objek Dokumen akan dibuat. Selama tahap konstruksi pohon, pohon DOM yang memiliki Dokumen dalam root-nya akan dimodifikasi dan elemen akan ditambahkan ke dalamnya. Setiap node yang dimunculkan oleh tokenizer akan diproses oleh konstruktor hierarki. Untuk setiap token, spesifikasi mendefinisikan elemen DOM mana yang relevan dengan token dan akan dibuat untuk token ini. Elemen ditambahkan ke hierarki DOM, dan juga tumpukan elemen terbuka. Tumpukan ini digunakan untuk memperbaiki ketidakcocokan bertingkat dan tag yang tidak ditutup. Algoritma ini juga dijelaskan sebagai mesin status. Status ini disebut "mode penyisipan".

Mari kita lihat proses konstruksi pohon untuk contoh input:

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

Input pada tahap konstruksi pohon adalah urutan token dari tahap tokenisasi. Mode pertama adalah "mode awal". Penerimaan token "html" akan menyebabkan peralihan ke mode "sebelum html" dan token diproses ulang dalam mode tersebut. Ini akan menyebabkan pembuatan elemen HTMLHTMLElement, yang akan ditambahkan ke objek Dokumen root.

Status akan diubah menjadi "sebelum head". Token "body" kemudian diterima. HTMLHeadElement akan dibuat secara implisit meskipun kita tidak memiliki token "head" dan akan ditambahkan ke hierarki.

Sekarang kita beralih ke mode "in head" dan kemudian ke "after head". Token isi diproses ulang, HTMLBodyElement dibuat dan disisipkan, serta modenya ditransfer ke "dalam isi".

Token karakter dari string "Hello world" kini diterima. Yang pertama akan menyebabkan pembuatan dan penyisipan node "Text" dan karakter lainnya akan ditambahkan ke node tersebut.

Penerimaan token akhir isi akan menyebabkan transfer ke mode "setelah tubuh". Sekarang kita akan menerima tag akhir HTML yang akan memindahkan kita ke mode "after after body". Menerima akhir token file akan mengakhiri penguraian.

Konstruksi pohon dari HTML contoh.
Gambar 11: konstruksi pohon contoh html

Tindakan saat penguraian selesai

Pada tahap ini, browser akan menandai dokumen sebagai interaktif dan mulai mengurai skrip yang berada dalam mode "ditangguhkan": skrip yang harus dijalankan setelah dokumen diurai. Status dokumen kemudian akan ditetapkan ke "complete" dan peristiwa "load" akan diaktifkan.

Anda dapat melihat algoritma lengkap untuk tokenisasi dan konstruksi pohon di spesifikasi HTML5.

Toleransi error browser

Anda tidak pernah mendapatkan error "Sintaksis Tidak Valid" di halaman HTML. Browser akan memperbaiki konten yang tidak valid dan terus digunakan.

Contohnya HTML ini:

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

Saya pasti telah melanggar sekitar sejuta aturan ("mytag" bukan tag standar, salah menempatkan elemen "p" dan "div" serta masih banyak lagi) tetapi browser masih menunjukkannya dengan benar dan tidak mengeluh. Jadi, banyak kode parser memperbaiki kesalahan penulis HTML.

Penanganan {i>error<i} cukup konsisten dalam browser, tetapi yang luar biasa masalah itu belum menjadi bagian dari spesifikasi HTML. Seperti penandaan dan tombol {i>back/forward<i}, ini adalah sesuatu yang dikembangkan di {i>browser<i} selama bertahun-tahun. Ada konstruksi HTML tidak valid yang diketahui dan diulang di banyak situs, dan browser mencoba memperbaikinya dengan cara yang sesuai dengan browser lain.

Spesifikasi HTML5 menentukan beberapa persyaratan ini. (WebKit merangkum ini dengan baik dalam komentar di awal kelas parser HTML.)

Parser mengurai input yang di-token ke dalam dokumen, sehingga membangun hierarki dokumen. Jika dokumen tersusun dengan baik, Anda dapat menguraikannya dengan mudah.

Sayangnya, kita harus menangani banyak dokumen HTML yang tidak tersusun dengan baik, sehingga parser harus toleran terhadap error.

Kita harus menangani setidaknya kondisi error berikut:

  1. Elemen yang ditambahkan secara eksplisit dilarang di dalam beberapa tag luar. Dalam hal ini, kita harus menutup semua tag hingga tag yang melarang elemen tersebut, lalu menambahkannya setelahnya.
  2. Kita tidak diizinkan menambahkan elemen secara langsung. Bisa jadi orang yang menulis dokumen lupa beberapa tag di antaranya (atau tag di antaranya bersifat opsional). Ini bisa terjadi dengan tag berikut: HTML HEAD BODY TBODY TR TD LI (apakah saya lupa?).
  3. Kita ingin menambahkan elemen blok di dalam elemen inline. Tutup semua elemen inline hingga elemen blok yang lebih tinggi berikutnya.
  4. Jika tidak berhasil, tutup elemen sampai kami diizinkan menambahkan elemen - atau abaikan tag.

Mari kita lihat beberapa contoh toleransi kesalahan WebKit:

</br> bukan <br>

Beberapa situs menggunakan </br>, bukan <br>. Agar kompatibel dengan IE dan Firefox, WebKit memperlakukan ini seperti <br>.

Kode:

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

Perhatikan bahwa penanganan error bersifat internal: error tidak akan ditampilkan kepada pengguna.

Tabel menyimpang

Tabel menyimpang adalah tabel di dalam tabel lain, tetapi tidak di dalam sel tabel.

Contoh:

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

WebKit akan mengubah hierarki menjadi dua tabel seinduk:

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

Kode:

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

WebKit menggunakan stack untuk konten elemen saat ini: WebKit akan memunculkan tabel dalam dari stack tabel luar. Tabel sekarang akan menjadi saudara kandung.

Elemen formulir bertingkat

Jika pengguna memasukkan formulir di dalam formulir lain, formulir kedua akan diabaikan.

Kode:

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

Hierarki tag yang terlalu dalam

Komentar tersebut sudah jelas.

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;
}

Tag akhir isi atau html salah tempat

Sekali lagi - komentar tersebut langsung berbicara.

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

Jadi, waspadalah - kecuali jika Anda ingin muncul sebagai contoh dalam cuplikan kode toleransi kesalahan WebKit - tulislah HTML dengan format yang baik.

Penguraian CSS

Ingat konsep penguraian dalam pendahuluan? Tidak seperti HTML, CSS adalah tata bahasa bebas konteks dan dapat diurai menggunakan jenis parser yang dijelaskan dalam pendahuluan. Bahkan, spesifikasi CSS menentukan tata bahasa leksikal dan sintaksis CSS.

Mari kita lihat beberapa contohnya:

Tata bahasa leksik (kosa kata) didefinisikan oleh ekspresi reguler untuk setiap token:

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" adalah singkatan dari ID, seperti nama class. "name" adalah id elemen (yang disebut "#" )

Tata bahasa sintaksnya dijelaskan dalam 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*] ')' ]
  ;

Penjelasan:

Kumpulan aturan adalah struktur ini:

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

div.error dan a.error adalah pemilih. Bagian di dalam tanda kurung kurawal berisi aturan yang diterapkan oleh kumpulan aturan ini. Struktur ini ditentukan secara formal dalam definisi ini:

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

Ini berarti kumpulan aturan adalah pemilih atau, secara opsional, sejumlah pemilih yang dipisahkan oleh koma dan spasi (S adalah singkatan dari ruang putih). Kumpulan aturan berisi tanda kurung kurawal dan di dalamnya ada deklarasi atau, secara opsional, sejumlah deklarasi yang dipisahkan oleh titik koma. “deklarasi” dan “{i>selector<i}” akan ditentukan dalam definisi BNF berikut.

Parser CSS WebKit

WebKit menggunakan generator parser Flex and Bison untuk membuat parser secara otomatis dari file tata bahasa CSS. Seperti yang Anda ingat dari pengantar parser, Bison akan membuat parser pengurangan shift bottom up. Firefox menggunakan parser top down yang ditulis secara manual. Dalam kedua kasus tersebut, setiap file CSS diuraikan menjadi objek StyleSheet. Setiap objek berisi aturan CSS. Objek aturan CSS berisi objek pemilih dan deklarasi, serta objek lain yang sesuai dengan tata bahasa CSS.

Mengurai CSS.
Gambar 12: mengurai CSS

Urutan pemrosesan untuk skrip dan lembar gaya

Skrip

Model web bersifat sinkron. Penulis berharap skrip akan diuraikan dan dieksekusi segera saat parser mencapai tag <script>. Penguraian dokumen akan dihentikan hingga skrip telah dieksekusi. Jika skrip berada di luar, resource harus diambil dari jaringan terlebih dahulu - tindakan ini juga dilakukan secara sinkron, dan penguraian akan terhenti hingga resource diambil. Ini adalah model selama bertahun-tahun dan juga ditentukan dalam spesifikasi HTML4 dan 5. Penulis dapat menambahkan atribut "menunda" ke skrip, sehingga atribut tersebut tidak akan menghentikan penguraian dokumen dan akan dijalankan setelah dokumen diurai. HTML5 menambahkan opsi untuk menandai skrip sebagai asinkron sehingga akan dapat diurai dan dieksekusi oleh thread yang berbeda.

Penguraian spekulatif

Baik WebKit maupun Firefox melakukan pengoptimalan ini. Saat mengeksekusi skrip, thread lain mengurai sisa dokumen dan mencari tahu resource lain apa yang perlu dimuat dari jaringan dan memuatnya. Dengan cara ini, resource dapat dimuat pada koneksi paralel dan kecepatan secara keseluruhan dapat ditingkatkan. Catatan: parser spekulatif hanya mengurai referensi ke sumber daya eksternal seperti skrip eksternal, lembar gaya, dan gambar: parser tidak memodifikasi pohon DOM - yang diserahkan ke parser utama.

Lembar gaya

Di sisi lain, lembar gaya memiliki model yang berbeda. Secara konseptual terlihat bahwa karena lembar gaya tidak mengubah hierarki DOM, tidak ada alasan untuk menunggunya dan menghentikan penguraian dokumen. Namun, ada masalah skrip yang meminta informasi gaya selama tahap penguraian dokumen. Jika gaya belum dimuat dan diuraikan, skrip akan mendapatkan jawaban yang salah dan tampaknya hal ini menyebabkan banyak masalah. Tampaknya merupakan kasus ekstrem, tetapi cukup umum. Firefox memblokir semua skrip saat ada style sheet yang masih dimuat dan diuraikan. WebKit hanya memblokir skrip saat mencoba mengakses properti gaya tertentu yang mungkin terpengaruh oleh lembar gaya yang tidak dimuat.

Merender konstruksi pohon

Ketika pohon DOM sedang dibuat, browser akan membangun pohon lainnya, yaitu pohon render. Hierarki ini merupakan elemen visual sesuai urutan tampilannya. Model ini adalah representasi visual dokumen. Tujuan pohon ini adalah untuk memungkinkan pengecatan konten dalam urutan yang benar.

Firefox menyebut elemen dalam pohon render sebagai "frames". WebKit menggunakan istilah perender atau objek render.

Perender mengetahui cara menata dan melukis dirinya sendiri dan turunannya.

Class RenderObject WebKit, class dasar perender, memiliki definisi berikut:

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
}

Setiap perender mewakili area persegi panjang yang biasanya sesuai dengan kotak CSS node, sebagaimana dijelaskan oleh spesifikasi CSS2. Perender ini berisi informasi geometris seperti lebar, tinggi, dan posisi.

Jenis kotak dipengaruhi oleh nilai "tampilan" atribut gaya yang relevan dengan node (lihat bagian komputasi gaya). Berikut ini kode WebKit untuk memutuskan jenis perender yang harus dibuat untuk node DOM, berdasarkan atribut tampilan:

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;
}

Jenis elemen juga dipertimbangkan: misalnya, kontrol formulir dan tabel memiliki bingkai khusus.

Di WebKit, jika elemen ingin membuat perender khusus, elemen tersebut akan mengganti metode createRenderer(). Perender mengarah ke objek gaya yang berisi informasi non-geometri.

Hubungan hierarki render dengan hierarki DOM

Perender sesuai dengan elemen DOM, tetapi relasinya bukan satu ke satu. Elemen DOM non-visual tidak akan disisipkan dalam hierarki render. Contohnya adalah elemen "head". Selain itu, elemen yang nilai tampilannya ditetapkan ke "none" tidak akan muncul dalam hierarki (sedangkan elemen dengan visibilitas "tersembunyi" akan muncul di hierarki).

Ada elemen DOM yang sesuai dengan beberapa objek visual. Ini biasanya merupakan elemen dengan struktur kompleks yang tidak dapat dijelaskan dengan satu persegi panjang. Misalnya, elemen "select" memiliki tiga perender: satu untuk area tampilan, satu untuk kotak daftar drop-down, dan satu untuk tombol. Selain itu, jika teks dipecah menjadi beberapa baris karena lebar tidak cukup untuk satu baris, baris baru akan ditambahkan sebagai perender tambahan.

Contoh lain dari beberapa perender adalah HTML yang rusak. Menurut spesifikasi CSS, elemen inline harus berisi hanya elemen blok atau hanya elemen inline. Dalam kasus konten campuran, perender blok anonim akan dibuat untuk menggabungkan elemen inline.

Beberapa objek render berhubungan dengan simpul DOM tetapi tidak di tempat yang sama dalam hierarki. Elemen mengambang dan yang diposisikan secara mutlak berada di luar alur, ditempatkan di bagian hierarki yang berbeda, dan dipetakan ke bingkai sebenarnya. Bingkai {i>placeholder<i} adalah di mana mereka seharusnya berada.

Hierarki render dan hierarki DOM yang sesuai.
Gambar 13: Hierarki render dan hierarki DOM yang sesuai. "Area pandang" adalah blok penampung awal. Di WebKit, objek tersebut akan menjadi objek "RenderView"

Alur pembangunan pohon

Di Firefox, presentasi didaftarkan sebagai pemroses untuk pembaruan DOM. Presentasi mendelegasikan pembuatan frame ke FrameConstructor dan konstruktor me-resolve gaya (lihat komputasi gaya) dan membuat frame.

Dalam WebKit proses penyelesaian gaya dan pembuatan perender disebut "lampiran". Setiap node DOM memiliki metode "melampirkan". Lampiran bersifat sinkron, penyisipan node ke hierarki DOM akan memanggil metode "attach" node baru.

Pemrosesan tag html dan body akan menghasilkan konstruksi root hierarki render. Objek render root sesuai dengan apa yang disebut spesifikasi CSS sebagai blok yang memuatnya: blok paling atas yang berisi semua blok lainnya. Dimensinya adalah area pandang: dimensi area tampilan jendela browser. Firefox menyebutnya ViewPortFrame dan WebKit menyebutnya RenderView. Ini adalah objek render yang ditunjuk dokumen. Bagian hierarki lainnya dibuat sebagai penyisipan simpul DOM.

Lihat spesifikasi CSS2 tentang model pemrosesan.

Komputasi gaya

Membuat pohon render memerlukan penghitungan properti visual setiap objek render. Hal ini dilakukan dengan menghitung properti gaya setiap elemen.

Gaya mencakup lembar gaya dari berbagai asal, elemen gaya inline, dan properti visual dalam HTML (seperti properti "bgcolor").Desain yang berikutnya akan diterjemahkan ke properti gaya CSS yang sesuai.

Asal lembar gaya adalah lembar gaya default browser, lembar gaya yang disediakan oleh penulis laman dan lembar gaya pengguna - ini adalah lembar gaya yang disediakan oleh pengguna browser (browser memungkinkan Anda menentukan gaya favorit Anda. Di Firefox, misalnya, hal ini dilakukan dengan menempatkan lembar gaya di folder "Firefox Profile").

Komputasi gaya memunculkan beberapa kesulitan:

  1. Data gaya adalah konstruksi yang sangat besar yang menampung banyak properti gaya. Hal ini dapat menyebabkan masalah memori.
  2. Menemukan aturan yang cocok untuk setiap elemen dapat menyebabkan masalah performa jika tidak dioptimalkan. Menelusuri seluruh daftar aturan untuk setiap elemen guna menemukan kecocokan adalah tugas yang berat. Pemilih dapat memiliki struktur rumit yang dapat menyebabkan proses pencocokan dimulai di jalur yang tampaknya menjanjikan yang terbukti sia-sia dan jalur lain harus dicoba.

    Misalnya - pemilih gabungan ini:

    div div div div{
    ...
    }
    

    Artinya aturan berlaku untuk <div> yang merupakan turunan dari 3 div. Misalnya Anda ingin memeriksa apakah aturan berlaku untuk elemen <div> tertentu. Anda memilih jalur tertentu ke atas pohon untuk diperiksa. Anda mungkin perlu melintasi pohon node ke atas hanya untuk mengetahui bahwa hanya ada dua div dan aturan tersebut tidak berlaku. Anda kemudian perlu mencoba jalur lain pada hierarki.

  3. Menerapkan aturan melibatkan aturan menurun yang cukup rumit yang menentukan hierarki aturan.

Mari kita lihat bagaimana browser menghadapi masalah ini:

Berbagi data gaya

Node WebKit merujuk ke objek gaya (RenderStyle). Objek ini dapat dibagikan oleh node dalam beberapa kondisi. Node tersebut adalah saudara kandung atau sepupu dan:

  1. Elemen harus dalam keadaan mouse yang sama (misalnya, satu elemen tidak boleh berada dalam :arahkan kursor sedangkan elemen lainnya tidak)
  2. Tidak ada elemen yang harus memiliki id
  3. Nama tag harus cocok
  4. Atribut class harus cocok
  5. Kumpulan atribut yang dipetakan harus sama
  6. Status link harus cocok
  7. Status fokus harus sesuai
  8. Tidak ada elemen yang akan terpengaruh oleh pemilih atribut. Yang terpengaruh adalah memiliki kecocokan pemilih yang menggunakan pemilih atribut di posisi mana pun dalam pemilih
  9. Tidak boleh ada atribut gaya inline pada elemen
  10. Tidak boleh ada pemilih seinduk yang digunakan sama sekali. WebCore cukup melempar tombol global ketika pemilih saudara ditemui dan menonaktifkan berbagi gaya untuk seluruh dokumen saat ada. Ini termasuk pemilih + dan pemilih seperti :first-child dan :last-child.

Hierarki aturan Firefox

Firefox memiliki dua pohon tambahan untuk memudahkan komputasi gaya: pohon aturan dan pohon konteks gaya. WebKit juga memiliki objek gaya namun tidak disimpan dalam hierarki seperti pohon konteks gaya, hanya simpul DOM yang menunjuk ke gaya relevannya.

Pohon konteks gaya Firefox.
Gambar 14: Hierarki konteks gaya Firefox.

Konteks gaya berisi nilai akhir. Nilai dihitung dengan menerapkan semua aturan yang cocok dalam urutan yang benar dan melakukan manipulasi yang mengubahnya dari nilai logis ke konkret. Misalnya, jika nilai logis adalah persentase layar, nilai tersebut akan dihitung dan diubah menjadi unit absolut. Ide pohon aturan sangat cerdas. Hal ini memungkinkan berbagi nilai antar-node untuk menghindari komputasi lagi. Tindakan ini juga menghemat ruang.

Semua aturan yang cocok akan disimpan dalam hierarki. Node bawah di jalur memiliki prioritas yang lebih tinggi. Hierarki berisi semua jalur untuk kecocokan aturan yang ditemukan. Menyimpan aturan dilakukan dengan lambat. Hierarki tersebut tidak dihitung di awal untuk setiap node, tetapi setiap kali gaya node perlu dihitung, jalur yang dihitung akan ditambahkan ke hierarki.

Idenya adalah melihat jalur pohon sebagai kata dalam leksikon. Katakanlah kita sudah menghitung hierarki aturan ini:

Hierarki aturan komputasi
Gambar 15: Hierarki aturan komputasi.

Misalkan kita perlu mencocokkan aturan untuk elemen lain dalam hierarki konten, dan mencari tahu aturan yang cocok (dalam urutan yang benar) adalah B-E-I. Kita sudah memiliki jalur ini dalam hierarki karena kita telah menghitung jalur A-B-E-I-L. Sekarang pekerjaan kita jadi lebih sedikit.

Mari kita lihat bagaimana pohon itu menyelamatkan pekerjaan kita.

Membagi menjadi beberapa struct

Konteks gaya dibagi menjadi beberapa struct. Struct tersebut berisi informasi gaya untuk kategori tertentu seperti batas atau warna. Semua properti dalam struct dapat diwariskan atau tidak diwarisi. Properti turunan adalah properti yang diwarisi dari induknya, kecuali jika ditentukan oleh elemen. Properti yang tidak diwarisi (disebut properti "reset") akan menggunakan nilai default jika tidak ditentukan.

Pohon ini membantu kita dengan meng-cache seluruh struct (berisi nilai akhir yang dihitung) dalam hierarki. Idenya adalah jika node bawah tidak memberikan definisi untuk struct, struct yang di-cache pada node atas dapat digunakan.

Menghitung konteks gaya menggunakan hierarki aturan

Saat menghitung konteks gaya untuk elemen tertentu, pertama-tama kita akan menghitung jalur di hierarki aturan atau menggunakan yang sudah ada. Kemudian, kita mulai menerapkan aturan di jalur tersebut untuk mengisi struct dalam konteks gaya yang baru. Kita mulai dari node bawah jalur - node dengan prioritas tertinggi (biasanya pemilih paling spesifik) dan melintasi pohon ke atas hingga struct kita penuh. Jika tidak ada spesifikasi untuk struct dalam node aturan tersebut, maka kita dapat mengoptimalkan secara signifikan - kita naik pohon sampai kita menemukan node yang menentukannya sepenuhnya dan menunjuk ke sana - itu adalah pengoptimalan terbaik - seluruh struct dibagikan. Tindakan ini akan menghemat komputasi nilai akhir dan memori.

Jika menemukan definisi parsial, kita naik ke hierarki sampai struct terisi.

Jika kita tidak menemukan definisi untuk struct, jika struct adalah jenis yang "diwarisi", kita akan merujuk ke struct induk kita di hierarki konteks. Dalam hal ini, kami juga berhasil membagikan struct. Jika itu adalah struct reset, maka nilai default akan digunakan.

Jika {i>node<i} yang paling spesifik menambahkan nilai maka kita perlu melakukan beberapa penghitungan tambahan untuk mengubahnya ke nilai aktual. Kemudian, kita akan meng-cache hasilnya di node hierarki sehingga dapat digunakan oleh turunan.

Jika elemen memiliki saudara kandung atau saudara laki-laki yang menunjuk ke node pohon yang sama, seluruh konteks gaya dapat dibagikan di antara keduanya.

Mari kita lihat contohnya: Misalkan kita memiliki kode HTML ini

<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>

Dan aturan berikut:

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

Untuk menyederhanakan semuanya, katakanlah kita hanya perlu mengisi dua struct: warna struct dan struct margin. Struct warna hanya berisi satu anggota: warna Struktur margin berisi empat sisi.

Hierarki aturan yang dihasilkan akan terlihat seperti ini (node ditandai dengan nama node: nomor aturan yang ditunjuk):

Hierarki aturan
Gambar 16: Hierarki aturan

Hierarki konteks akan terlihat seperti ini (nama node: node aturan yang ditunjuk):

Hierarki konteks.
Gambar 17: Hierarki konteks

Misalkan kita mengurai HTML dan mendapatkan tag <div> kedua. Kita perlu membuat konteks gaya untuk node ini dan mengisi struct gayanya.

Kita akan mencocokkan aturan dan mendapati bahwa aturan yang cocok untuk <div> adalah 1, 2, dan 6. Ini berarti sudah ada jalur di hierarki yang dapat digunakan elemen kita dan kita hanya perlu menambahkan {i>node<i} lain untuk aturan 6 (node F di pohon aturan).

Kita akan membuat konteks gaya dan menempatkannya di pohon konteks. Konteks gaya baru akan mengarah ke node F dalam hierarki aturan.

Sekarang kita perlu mengisi struct gaya. Kita akan mulai dengan mengisi struct margin. Karena node aturan terakhir (F) tidak ditambahkan ke struct margin, kita dapat naik hierarki sampai menemukan struct yang di-cache yang dihitung dalam penyisipan node sebelumnya dan menggunakannya. Kita akan menemukannya di {i>node<i} B, yang merupakan {i>node<i} paling atas yang menentukan aturan margin.

Kita memiliki definisi untuk struct warna, jadi kita tidak dapat menggunakan struct yang di-cache. Karena warna memiliki satu atribut, kita tidak perlu naik ke hierarki untuk mengisi atribut lainnya. Kita akan menghitung nilai akhir (mengonversi string ke RGB, dll.) dan meng-cache struct yang dihitung pada node ini.

Pekerjaan pada elemen <span> kedua menjadi lebih mudah. Kita akan mencocokkan aturan dan sampai pada kesimpulan bahwa aturan tersebut menunjuk ke aturan G, seperti span sebelumnya. Karena kita memiliki saudara yang menunjuk ke node yang sama, kita dapat membagikan seluruh konteks gaya dan hanya menunjuk ke konteks span sebelumnya.

Untuk struct yang berisi aturan yang diwarisi dari induknya, penyimpanan dalam cache dilakukan pada pohon konteks (properti warna sebenarnya diwariskan, tetapi Firefox memperlakukannya sebagai reset dan meng-cache-nya di hierarki aturan).

Misalnya, jika kita menambahkan aturan untuk font dalam paragraf:

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

Kemudian elemen paragraf, yang merupakan turunan dari div dalam hierarki konteks, dapat berbagi struct font yang sama dengan induknya. Hal ini terjadi jika tidak ada aturan font yang ditentukan untuk paragraf.

Di WebKit, yang tidak memiliki hierarki aturan, deklarasi yang cocok akan dilewati empat kali. Properti prioritas tinggi yang tidak penting pertama diterapkan (properti yang harus diterapkan terlebih dahulu karena properti lain bergantung padanya, seperti tampilan), kemudian prioritas tinggi penting, kemudian aturan prioritas normal tidak penting, kemudian aturan penting prioritas normal. Artinya, properti yang muncul beberapa kali akan diselesaikan sesuai dengan urutan menurun yang benar. Yang terakhir menang.

Jadi singkatnya: membagikan objek gaya (seluruhnya atau beberapa struct di dalamnya) akan menyelesaikan masalah 1 dan 3. Pohon aturan Firefox juga membantu menerapkan properti dengan urutan yang benar.

Memanipulasi aturan untuk memudahkan pencocokan

Ada beberapa sumber untuk aturan gaya:

  1. Aturan CSS, baik dalam lembar gaya eksternal atau di elemen gaya. css p {color: blue}
  2. Atribut gaya inline seperti html <p style="color: blue" />
  3. Atribut visual HTML (yang dipetakan ke aturan gaya yang relevan) html <p bgcolor="blue" /> Dua yang terakhir mudah dicocokkan dengan elemen karena ia memiliki atribut gaya dan atribut HTML dapat dipetakan menggunakan elemen sebagai kunci.

Seperti yang disebutkan sebelumnya dalam masalah #2, pencocokan aturan CSS bisa lebih rumit. Untuk mengatasi kesulitan, aturan dimanipulasi agar lebih mudah diakses.

Setelah mengurai style sheet, aturan ditambahkan ke salah satu dari beberapa peta hash, sesuai dengan pemilih. Ada peta menurut ID, nama kelas, nama tag, dan peta umum untuk apa pun yang tidak sesuai dengan kategori tersebut. Jika pemilih adalah ID, aturan akan ditambahkan ke peta ID. Jika berupa class, aturan akan ditambahkan ke peta class, dll.

Manipulasi ini membuatnya lebih mudah untuk mencocokkan aturan. Tidak perlu melihat di setiap deklarasi: kita dapat mengekstrak aturan yang relevan untuk elemen dari peta. Pengoptimalan ini meniadakan aturan sebanyak 95+%, sehingga aturan tersebut tidak perlu dipertimbangkan selama proses pencocokan(4.1).

Mari kita lihat misalnya aturan gaya berikut:

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

Aturan pertama akan disisipkan ke dalam peta class. Yang kedua masuk ke peta ID dan yang ketiga masuk ke peta tag.

Untuk fragmen HTML berikut;

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

Pertama-tama, kita akan mencoba menemukan aturan untuk elemen p. Peta kelas akan berisi kunci "error" tempat aturan untuk "p.error" ditemukan. Elemen div akan memiliki aturan yang relevan dalam id map (kuncinya adalah id) dan peta tag. Jadi satu-satunya pekerjaan yang tersisa adalah mencari tahu aturan mana yang diekstrak oleh kunci yang benar-benar cocok.

Misalnya jika aturan untuk div adalah:

table div {margin: 5px}

Itu masih akan diekstrak dari peta tag, karena kuncinya adalah pemilih paling kanan, tetapi tidak akan cocok dengan elemen div kita, yang tidak memiliki ancestor tabel.

Baik WebKit maupun Firefox melakukan manipulasi ini.

Urutan jenjang {i>style sheet<i}

Objek gaya memiliki properti yang sesuai dengan setiap atribut visual (semua atribut CSS tetapi lebih umum). Jika properti tidak ditetapkan oleh salah satu aturan yang cocok, beberapa properti dapat diwarisi oleh objek gaya elemen induk. Properti lain memiliki nilai default.

Masalahnya berawal ketika ada lebih dari satu definisi - inilah urutan menurun untuk menyelesaikan masalah.

Deklarasi untuk properti gaya dapat muncul di beberapa lembar gaya, dan beberapa kali di dalam lembar gaya. Artinya, urutan penerapan aturan sangatlah penting. Hal ini disebut urutan "jenjang". Menurut spesifikasi CSS2, urutan menurun adalah (dari rendah ke tinggi):

  1. Deklarasi browser
  2. Deklarasi normal pengguna
  3. Membuat pernyataan normal
  4. Membuat pernyataan penting terkait penulis
  5. Pernyataan penting pengguna

Deklarasi browser tidak begitu penting dan pengguna mengganti penulis hanya jika deklarasi ditandai sebagai penting. Pernyataan dengan urutan yang sama akan diurutkan berdasarkan kekhususan lalu urutannya akan ditentukan. Atribut visual HTML diterjemahkan ke dalam deklarasi CSS yang cocok . Aturan tersebut diperlakukan sebagai aturan penulis dengan prioritas rendah.

Kekhususan

Kekhususan pemilih ditentukan oleh spesifikasi CSS2 sebagai berikut:

  1. hitungan 1 jika deklarasi asalnya adalah atribut 'style' dan bukan aturan dengan pemilih, 0 jika sebaliknya (= a)
  2. menghitung jumlah atribut ID di pemilih (= b)
  3. menghitung jumlah atribut lain dan kelas-pseudo dalam pemilih (= c)
  4. menghitung jumlah nama elemen dan elemen semu di pemilih (= d)

Menggabungkan empat angka a-b-c-d (dalam sistem bilangan dengan basis yang besar) memberikan kekhususannya.

Basis angka yang perlu Anda gunakan ditentukan berdasarkan jumlah tertinggi yang Anda miliki di salah satu kategori.

Misalnya, jika a=14 Anda dapat menggunakan basis heksadesimal. Dalam kasus yang jarang terjadi di mana a=17, Anda akan membutuhkan basis angka 17 digit. Situasi selanjutnya bisa terjadi dengan pemilih seperti ini: html body div div p... (17 tag di pemilih Anda... tidak terlalu mungkin).

Beberapa contoh:

 *             {}  /* 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 */

Mengurutkan aturan

Setelah dicocokkan, aturan tersebut diurutkan sesuai dengan aturan menurun. WebKit menggunakan penyortiran balon untuk daftar kecil dan penggabungan pengurutan untuk daftar besar. WebKit menerapkan pengurutan dengan mengganti operator > untuk aturan:

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;
}

Proses bertahap

WebKit menggunakan tanda yang menandai apakah semua lembar gaya tingkat atas (termasuk @imports) telah dimuat. Jika gaya tidak dimuat sepenuhnya saat dilampirkan, holder tempat akan digunakan dan ditandai dalam dokumen, dan akan dihitung ulang setelah lembar gaya dimuat.

Tata Letak

Saat dibuat dan ditambahkan ke hierarki, perender tidak memiliki posisi dan ukuran. Menghitung nilai ini disebut layout atau reflow.

HTML menggunakan model tata letak berbasis alur, yang berarti bahwa sebagian besar waktu dimungkinkan untuk menghitung geometri dalam satu penerusan. Elemen berikutnya "dalam alur" biasanya tidak memengaruhi geometri elemen yang sebelumnya "dalam alur", sehingga tata letak dapat dilanjutkan dari kiri ke kanan, dari atas ke bawah melalui dokumen. Ada pengecualian: misalnya, tabel HTML mungkin memerlukan lebih dari satu pass.

Sistem koordinat relatif terhadap frame root. Koordinat atas dan kiri digunakan.

Tata letak adalah proses rekursif. Dimulai di perender root, yang sesuai dengan elemen <html> dokumen HTML. Tata letak berlanjut secara rekursif melalui beberapa atau semua hierarki frame, dengan menghitung informasi geometris untuk setiap perender yang memerlukannya.

Posisi perender root adalah 0,0 dan dimensinya adalah area pandang - bagian yang terlihat dari jendela browser.

Semua perender memiliki metode "tata letak" atau "perubahan posisi/geometri", setiap perender memanggil metode tata letak turunannya yang memerlukan tata letak.

Sistem bit kotor

Agar tidak menata letak penuh untuk setiap perubahan kecil, browser menggunakan sistem "dirty bit". Perender yang diubah atau ditambahkan akan menandai dirinya dan turunannya sebagai "dirty": memerlukan tata letak.

Ada dua tanda: "dirty", dan "children are dirty" yang berarti meskipun perender itu sendiri mungkin baik-baik saja, setidaknya ada satu turunan yang memerlukan tata letak.

Tata letak global dan inkremental

Tata letak dapat dipicu di seluruh hierarki render - ini adalah tata letak "global". Hal ini dapat terjadi akibat:

  1. Perubahan gaya global yang memengaruhi semua perender, seperti perubahan ukuran font.
  2. Akibat ukuran layar yang diubah

Tata letak dapat bersifat inkremental, hanya perender kotor yang akan ditata (ini dapat menyebabkan beberapa kerusakan yang akan memerlukan tata letak tambahan).

Tata letak inkremental dipicu (secara asinkron) saat perender kotor. Misalnya, saat perender baru ditambahkan ke hierarki render setelah konten tambahan berasal dari jaringan dan ditambahkan ke hierarki DOM.

Tata letak inkremental.
Gambar 18: Tata letak inkremental - hanya perender kotor dan turunannya yang disusun

Tata letak Asinkron dan Sinkron

Tata letak inkremental dilakukan secara asinkron. Antrean "perintah ubah posisi" di Firefox untuk tata letak inkremental akan memicu eksekusi batch perintah ini. WebKit juga memiliki penghitung waktu yang menjalankan tata letak inkremental - struktur dilewati dan perender "kotor" tidak memiliki tata letak.

Skrip yang meminta informasi gaya, seperti "offsetHeight" dapat memicu tata letak inkremental secara sinkron.

Tata letak global biasanya akan dipicu secara sinkron.

Terkadang tata letak dipicu sebagai callback setelah tata letak awal karena beberapa atribut, seperti posisi scroll berubah.

Pengoptimalan

Jika tata letak dipicu oleh "ubah ukuran" atau perubahan posisi perender(dan bukan ukuran), ukuran render diambil dari cache dan tidak dihitung ulang...

Dalam beberapa kasus, hanya sub-hierarki yang diubah dan tata letak tidak dimulai dari root. Hal ini dapat terjadi jika perubahan bersifat lokal dan tidak memengaruhi lingkungannya - seperti teks yang disisipkan ke kolom teks (jika tidak, setiap penekanan tombol akan memicu tata letak mulai dari root).

Proses tata letak

Tata letak biasanya memiliki pola berikut:

  1. Perender induk menentukan lebarnya sendiri.
  2. Orang tua membesarkan anak-anak dan:
    1. Tempatkan perender turunan (menyetel x dan y-nya).
    2. Memanggil tata letak turunan jika diperlukan - tata letak tersebut kotor atau kita berada dalam tata letak global, atau karena beberapa alasan lain - yang menghitung tinggi turunan.
  3. Induk menggunakan tinggi akumulatif turunan serta tinggi margin dan padding untuk menyetel tingginya sendiri - ini akan digunakan oleh induk perender induk.
  4. Menyetel bit kotornya ke false (salah).

Firefox menggunakan objek "state" (nsHTMLReflowState) sebagai parameter untuk menata letak (disebut "reflow"). Di antaranya, status menyertakan lebar induk.

Output tata letak Firefox adalah objek "metrics" (nsHTMLReflowMetrics). Kolom ini akan berisi tinggi yang dihitung perender.

Penghitungan lebar

Lebar perender dihitung menggunakan lebar blok penampung, properti "lebar" gaya perender, margin, dan batas.

Misalnya, lebar div berikut:

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

Akan dihitung oleh WebKit sebagai berikut(class RenderBox method calcWidth):

  • Lebar penampung adalah maksimum penampung yang tersediaWidth dan 0. availableWidth dalam hal ini adalah contentWidth yang dihitung sebagai:
clientWidth() - paddingLeft() - paddingRight()

clientWidth dan clientHeight mewakili bagian dalam objek, kecuali batas dan scrollbar.

  • Lebar elemen adalah atribut gaya "width". Ini akan dihitung sebagai nilai absolut dengan menghitung persentase lebar container.

  • Batas horizontal dan padding sekarang ditambahkan.

Sejauh ini, ini adalah perhitungan "lebar yang diinginkan". Sekarang lebar minimum dan maksimum akan dihitung.

Jika lebar yang dipilih lebih besar dari lebar maksimum, lebar maksimum akan digunakan. Jika kurang dari lebar minimum (unit terkecil yang tidak dapat dipecah), maka lebar minimum digunakan.

Nilai-nilai di-cache jika tata letak diperlukan, tetapi lebar tidak berubah.

Jeda Baris

Jika perender di tengah tata letak memutuskan bahwa perender harus berhenti berfungsi, perender akan berhenti dan menyebar ke induk tata letak yang harus diubah. Induk membuat perender tambahan dan memanggil tata letak di dalamnya.

Lukisan

Pada tahap menggambar, pohon render akan dilalui dan metode "paint()" perender dipanggil untuk menampilkan konten pada layar. Painting menggunakan komponen infrastruktur UI.

Global dan inkremental

Seperti tata letak, penggambaran juga bisa bersifat global - seluruh pohon dilukis - atau inkremental. Dalam penggambaran inkremental, beberapa perender berubah dengan cara yang tidak memengaruhi seluruh hierarki. Perender yang diubah akan membatalkan validasi persegi panjangnya di layar. Hal ini menyebabkan OS melihatnya sebagai "area kotor" dan menghasilkan peristiwa "paint". OS melakukannya dengan cerdas dan menggabungkan beberapa region menjadi satu. Di Chrome, hal ini lebih rumit karena perender berada dalam proses yang berbeda dengan proses utama. Chrome menyimulasikan perilaku OS sampai batas tertentu. Presentasi mendengarkan kejadian ini dan mendelegasikan pesan ke akar render. Hierarki akan dilewati hingga perender yang relevan tercapai. Kode itu akan melakukan repaint sendiri (dan biasanya turunannya).

Urutan pengecatan

CSS2 menentukan urutan proses menggambar. Ini sebenarnya urutan elemen ditumpuk dalam konteks penumpukan. Urutan ini memengaruhi pengecatan karena tumpukan dicat dari belakang ke depan. Urutan tumpukan perender blok adalah:

  1. warna latar belakang
  2. gambar latar
  3. border
  4. children
  5. outline

Daftar tampilan Firefox

Firefox berjalan di atas pohon render dan membuat daftar tampilan untuk persegi panjang yang digambar. File ini berisi perender yang relevan untuk persegi panjang, dalam urutan penggambaran yang tepat (latar belakang perender, lalu batas, dll.).

Dengan begitu, pohon hanya perlu dilalui sekali untuk pengecatan ulang, bukan beberapa kali - mengecat semua latar belakang, lalu semua gambar, lalu semua batas, dll.

Firefox mengoptimalkan proses ini dengan tidak menambahkan elemen yang akan disembunyikan, seperti elemen sepenuhnya di bawah elemen buram lainnya.

Penyimpanan persegi panjang WebKit

Sebelum menggambar ulang, WebKit menyimpan persegi panjang lama sebagai bitmap. Lalu, kode ini hanya melukiskan delta di antara persegi panjang baru dan lama.

Perubahan dinamis

Browser mencoba melakukan tindakan seminimal mungkin sebagai respons terhadap perubahan. Jadi, perubahan warna elemen hanya akan menyebabkan penggambaran ulang elemen. Perubahan pada posisi elemen akan menyebabkan layout dan penggambaran ulang elemen, turunannya, dan mungkin elemen seinduknya. Menambahkan simpul DOM akan menyebabkan tata letak dan penggambaran ulang simpul. Perubahan besar, seperti memperbesar ukuran font elemen "html", akan menyebabkan pembatalan cache, menata ulang tata letak dan menggambar ulang seluruh pohon.

Thread mesin rendering

Mesin rendering merupakan thread tunggal. Hampir semuanya, kecuali operasi jaringan, terjadi di satu thread. Di Firefox dan Safari, ini adalah thread utama browser. Di Chrome, ini adalah thread utama proses tab.

Operasi jaringan dapat dilakukan oleh beberapa thread paralel. Jumlah koneksi paralel terbatas (biasanya 2 - 6 koneksi).

Loop peristiwa

Thread utama browser adalah loop peristiwa. Ini adalah loop tak terbatas yang membuat proses tetap aktif. Fungsi ini menunggu peristiwa (seperti peristiwa layout dan paint) dan memprosesnya. Ini adalah kode Firefox untuk loop peristiwa utama:

while (!mExiting)
    NS_ProcessNextEvent(thread);

Model visual CSS2

Kanvas

Menurut spesifikasi CSS2, istilah kanvas menjelaskan "ruang tempat struktur pemformatan dirender": tempat browser melukis konten.

Kanvas tidak terbatas untuk setiap dimensi ruang, tetapi browser memilih lebar awal berdasarkan dimensi area pandang.

Menurut www.w3.org/TR/CSS2/zindex.html, kanvas itu transparan jika ditampung dalam yang lain, dan diberi warna yang ditentukan browser jika tidak.

Model CSS Box

Model kotak CSS menjelaskan kotak persegi panjang yang dihasilkan untuk elemen dalam hierarki dokumen dan ditata sesuai dengan model pemformatan visual.

Setiap kotak memiliki area konten (misalnya teks, gambar, dll.) dan area padding, batas, dan margin di sekitarnya yang bersifat opsional.

Model kotak CSS2
Gambar 19: Model kotak CSS2

Setiap node menghasilkan 0...n kotak tersebut.

Semua elemen memiliki properti "display" yang menentukan jenis kotak yang akan dibuat.

Contoh:

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

Defaultnya adalah inline, tetapi style sheet browser dapat menetapkan default lainnya. Misalnya: tampilan default untuk elemen "div" adalah blok.

Anda dapat menemukan contoh lembar gaya default di sini: www.w3.org/TR/CSS2/sample.html.

Skema pemosisian

Ada tiga skema:

  1. Normal: objek diposisikan sesuai dengan tempatnya dalam dokumen. Ini berarti tempatnya di hierarki render seperti tempatnya di hierarki DOM dan ditata sesuai dengan dimensi dan jenis kotaknya
  2. {i>Float<i}: mula-mula objek ditata seperti aliran normal, kemudian dipindahkan sejauh mungkin ke kiri atau ke kanan
  3. Absolut: objek diletakkan dalam hierarki render di tempat yang berbeda dengan di hierarki DOM

Skema pemosisian ditetapkan oleh properti "position" dan atribut "float".

  • statis dan relatif menyebabkan alur normal
  • absolut dan tetap menyebabkan pemosisian absolut

Dalam pemosisian statis, tidak ada posisi yang ditentukan dan pemosisian default digunakan. Dalam skema lain, penulis menentukan posisi: atas, bawah, kiri, kanan.

Cara kotak ditata ditentukan oleh:

  • Jenis kotak
  • Dimensi kotak
  • Skema pemosisian
  • Informasi eksternal seperti ukuran gambar dan ukuran layar

Jenis kotak

Kotak blok: membentuk blok - memiliki persegi panjang sendiri di jendela browser.

Kotak blok.
Gambar 20: Kotak blok

Kotak inline: tidak memiliki blok sendiri, tetapi berada di dalam blok penampung.

Kotak inline.
Gambar 21: Kotak inline

Blok diformat secara vertikal satu demi satu. Inline diformat secara horizontal.

Pemformatan blok dan inline.
Gambar 22: Pemformatan blok dan inline

Kotak inline ditempatkan di dalam garis atau "kotak baris". Garis-garis tersebut setidaknya setinggi kotak tertinggi tetapi bisa lebih tinggi, ketika kotak-kotak itu disejajarkan sebagai "dasar bawaan" - artinya bagian bawah elemen disejajarkan pada titik kotak lain selain bagian bawah. Jika lebar penampung tidak cukup, inline akan ditempatkan pada beberapa baris. Ini biasanya terjadi dalam sebuah paragraf.

Garis.
Gambar 23: Garis

Positioning

Relatif

Pemosisian relatif - diposisikan seperti biasa lalu dipindahkan oleh delta yang diperlukan.

Pemosisian relatif.
Gambar 24: Posisi relatif

Float

Kotak {i>float<i} digeser ke kiri atau kanan sebuah garis. Fitur yang menarik adalah kotak lainnya mengalir di sekelilingnya. HTML:

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

Akan terlihat seperti ini:

Mengambang.
Gambar 25: Float

Absolut dan tetap

Tata letak didefinisikan dengan tepat, terlepas dari flow normalnya. Elemen tidak berpartisipasi dalam alur normal. Dimensi bersifat relatif terhadap penampung. Secara tetap, penampung adalah area pandang.

Pemosisian tetap.
Gambar 26: Pemosisian tetap

Representasi berlapis

Ini ditentukan oleh properti CSS indeks z. Ini mewakili dimensi ketiga kotak: posisinya di sepanjang "sumbu z".

Kotak-kotak tersebut dibagi menjadi tumpukan (disebut konteks penumpukan). Di setiap tumpukan, elemen kembali akan digambar terlebih dahulu dan elemen maju di atasnya, lebih dekat ke pengguna. Jika terjadi tumpang tindih, elemen terpenting akan menyembunyikan elemen pertama.

Stack diurutkan sesuai dengan properti indeks z. Kotak dengan properti "indeks z" membentuk stack lokal. Area pandang memiliki stack luar.

Contoh:

<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>

Hasilnya adalah:

Pemosisian tetap.
Gambar 27: Pemosisian tetap

Meskipun simbol div merah mendahului div hijau di markup, dan telah digambar sebelumnya dalam flow reguler, properti indeks z lebih tinggi, sehingga lebih maju dalam stack yang dipegang oleh kotak root.

Referensi

  1. Arsitektur browser

    1. Grosskurth, Alan. Arsitektur Referensi untuk Browser Web (pdf)
    2. Gupta, Vineet. Cara Kerja Browser - Bagian 1 - Arsitektur
  2. Penguraian

    1. Aho, Sethi, Ullman, Compiler: Principles, Techniques, and Tools (alias "Dragon book"), Addison-Wesley, 1986
    2. Rick Jelliffe. The Bold and the Beautiful: dua draf baru untuk HTML 5.
  3. Firefox

    1. L. David Baron, Faster HTML and CSS: Layout Engine Internals for Web Developers.
    2. L. David Baron, Lebih cepat HTML dan CSS: Layout Engine Internal untuk Pengembang Web (video bincang-bincang teknologi Google)
    3. L. David Baron, Layout Engine di Mozilla
    4. L. David Baron, Dokumentasi Sistem Mozilla Style
    5. Chris Waterson, Catatan tentang Perubahan posisi/geometri HTML
    6. Chris Waterson, Ringkasan Gecko
    7. Alexander Larsson, The life of an HTML HTTP request
  4. WebKit

    1. David Hyatt, Menerapkan CSS(bagian 1)
    2. David Hyatt, Ringkasan WebCore
    3. David Hyatt, Rendering WebCore
    4. David Hyatt, Masalah FOUC
  5. Spesifikasi W3C

    1. Spesifikasi HTML 4.01
    2. Spesifikasi W3C HTML5
    3. Spesifikasi Casscading Style Sheets Level 2 Revisi 1 (CSS 2.1)
  6. Petunjuk build browser

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

Terjemahan

Halaman ini telah diterjemahkan ke dalam bahasa Jepang, dua kali:

Anda dapat melihat terjemahan bahasa Korea dan Turki yang dihosting secara eksternal.

Terima kasih semuanya.