Tampilan mendalam pada browser web modern (bagian 3)

Mariko Kosaka

Cara kerja internal Proses Perender

Ini adalah bagian 3 dari 4 bagian seri blog yang membahas cara kerja browser. Sebelumnya, kita telah membahas arsitektur multiproses dan alur navigasi. Dalam postingan ini, kita akan melihat apa yang terjadi di dalam proses perender.

Proses perender menyentuh banyak aspek performa web. Karena ada banyak hal yang terjadi di dalam proses perender, postingan ini hanyalah ringkasan umum. Jika Anda ingin mengetahui lebih dalam, bagian Performa di Dasar-Dasar Web memiliki banyak referensi lainnya.

Proses perender menangani konten web

Proses perender bertanggung jawab atas semua yang terjadi di dalam tab. Dalam proses perender, thread utama menangani sebagian besar kode yang Anda kirim ke pengguna. Terkadang bagian JavaScript ditangani oleh thread pekerja jika Anda menggunakan pekerja web atau pekerja layanan. Thread kompositor dan raster juga dijalankan di dalam proses perender untuk merender halaman secara efisien dan lancar.

Tugas inti proses perender adalah mengubah HTML, CSS, dan JavaScript menjadi halaman web yang dapat berinteraksi dengan pengguna.

Proses perender
Gambar 1: Proses perender dengan thread utama, thread pekerja, thread compositor, dan thread raster di dalamnya

Penguraian

Konstruksi DOM

Saat proses perender menerima pesan commit untuk navigasi dan mulai menerima data HTML, thread utama akan mulai mengurai string teks (HTML) dan mengubahnya menjadi Document Object Model (DOM).

DOM adalah representasi internal halaman dari browser, serta struktur data dan API yang dapat berinteraksi dengan developer web melalui JavaScript.

Mengurai dokumen HTML ke DOM ditentukan oleh Standar HTML. Anda mungkin memperhatikan bahwa memasukkan HTML ke browser tidak akan menghasilkan error. Misalnya, tag </p> penutup yang tidak ada adalah HTML yang valid. Markup yang salah seperti Hi! <b>I'm <i>Chrome</b>!</i> (tag b ditutup sebelum tag i) dianggap seolah-olah Anda menulis Hi! <b>I'm <i>Chrome</i></b><i>!</i>. Hal ini karena spesifikasi HTML didesain untuk menangani error tersebut dengan baik. Jika ingin tahu bagaimana semua hal ini dilakukan, Anda dapat membaca bagian "An introduction to error handle and mendapati case in the parser" di spesifikasi HTML.

Memuat subresource

Situs biasanya menggunakan resource eksternal seperti gambar, CSS, dan JavaScript. File tersebut perlu dimuat dari jaringan atau cache. Thread utama dapat memintanya satu per satu karena menemukannya saat mengurai untuk mem-build DOM, tetapi untuk mempercepat, "pemindai pramuat" dijalankan secara serentak. Jika ada hal seperti <img> atau <link> dalam dokumen HTML, pemindai pramuat akan mengintip token yang dihasilkan oleh parser HTML dan mengirimkan permintaan ke thread jaringan dalam proses browser.

DOM
Gambar 2: Thread utama yang mengurai HTML dan membuat hierarki DOM

JavaScript dapat memblokir penguraian

Saat menemukan tag <script>, parser HTML akan menjeda penguraian dokumen HTML dan harus memuat, mengurai, serta mengeksekusi kode JavaScript. Mengapa? karena JavaScript dapat mengubah bentuk dokumen menggunakan hal seperti document.write() yang mengubah seluruh struktur DOM (ringkasan model penguraian di spesifikasi HTML memiliki diagram yang bagus). Inilah sebabnya mengapa parser HTML harus menunggu JavaScript dijalankan sebelum dapat melanjutkan penguraian dokumen HTML. Jika Anda ingin tahu tentang apa yang terjadi dalam eksekusi JavaScript, tim V8 telah membahasnya dan membuat postingan blog tentang hal ini.

Petunjuk ke browser bagaimana Anda ingin memuat resource

Ada banyak cara developer web dapat mengirimkan petunjuk ke browser untuk memuat resource dengan baik. Jika JavaScript tidak menggunakan document.write(), Anda dapat menambahkan atribut async atau defer ke tag <script>. Selanjutnya, browser memuat dan menjalankan kode JavaScript secara asinkron dan tidak memblokir penguraian. Anda juga dapat menggunakan modul JavaScript jika sesuai. <link rel="preload"> adalah cara untuk memberi tahu browser bahwa resource sangat diperlukan untuk navigasi saat ini dan Anda ingin mendownload sesegera mungkin. Anda dapat membaca hal ini selengkapnya di Prioritas Resource – Menggunakan Browser untuk Membantu Anda.

Penghitungan gaya

Memiliki DOM tidak cukup untuk mengetahui tampilan halaman karena kita dapat menata gaya elemen halaman di CSS. Thread utama akan mengurai CSS dan menentukan gaya yang dihitung untuk setiap node DOM. Bagian ini adalah informasi tentang jenis gaya yang diterapkan ke setiap elemen berdasarkan pemilih CSS. Anda dapat melihat informasi ini di bagian computed pada DevTools.

Gaya komputasi
Gambar 3: CSS penguraian thread utama untuk menambahkan gaya komputasi

Bahkan jika Anda tidak memberikan CSS apa pun, setiap simpul DOM memiliki gaya yang dihitung. Tag <h1> ditampilkan lebih besar dari tag <h2> dan margin ditentukan untuk setiap elemen. Hal ini karena browser memiliki style sheet default. Jika ingin mengetahui seperti apa CSS default Chrome, Anda dapat melihat kode sumbernya di sini.

Tata Letak

Sekarang proses perender mengetahui struktur dokumen dan gaya untuk setiap node, tetapi itu tidak cukup untuk merender halaman. Bayangkan Anda mencoba mendeskripsikan lukisan kepada teman Anda melalui ponsel. "Ada lingkaran merah besar dan kotak biru kecil" tidak cukup informasi bagi teman Anda untuk mengetahui seperti apa tepatnya lukisan itu nantinya.

permainan mesin faks manusia
Gambar 4: Seseorang yang berdiri di depan lukisan dan saluran telepon terhubung dengan orang lain

Tata letak adalah proses untuk menemukan geometri elemen. Thread utama menelusuri DOM dan gaya terkomputasi, serta membuat hierarki tata letak yang berisi informasi seperti koordinat x y dan ukuran kotak pembatas. Hierarki tata letak mungkin mirip dengan hierarki DOM, tetapi hanya berisi informasi yang terkait dengan apa yang terlihat di halaman. Jika display: none diterapkan, elemen tersebut bukan bagian dari hierarki tata letak (tetapi, elemen dengan visibility: hidden akan ada dalam hierarki tata letak). Demikian pula, jika class pseudo dengan konten seperti p::before{content:"Hi!"} diterapkan, class tersebut akan disertakan dalam hierarki tata letak meskipun tidak ada dalam DOM.

tata letak
Gambar 5: Thread utama yang melewati hierarki DOM dengan gaya terkomputasi dan menghasilkan hierarki tata letak
Gambar 6: Tata letak kotak untuk paragraf yang berpindah karena perubahan pemisah baris

Menentukan Tata Letak laman adalah tugas yang menantang. Tata letak halaman yang paling sederhana seperti aliran blok dari atas ke bawah pun harus mempertimbangkan seberapa besar font dan letak pemisah baris karena hal itu memengaruhi ukuran dan bentuk paragraf; yang kemudian memengaruhi posisi paragraf berikutnya.

CSS dapat membuat elemen mengambang di satu sisi, menyamarkan item tambahan, dan mengubah arah penulisan. Anda dapat membayangkan, tahap tata letak ini memiliki tugas yang luar biasa. Di Chrome, seluruh tim engineer mengerjakan tata letak. Jika Anda ingin melihat detail karya mereka, beberapa diskusi dari BlinkOn Conference direkam dan cukup menarik untuk ditonton.

Cat

game menggambar
Gambar 7: Seseorang di depan kanvas memegang kuas, bertanya-tanya apakah dia harus menggambar lingkaran terlebih dahulu atau persegi

Memiliki DOM, gaya, dan tata letak masih belum cukup untuk merender halaman. Katakanlah Anda mencoba mereproduksi sebuah lukisan. Anda mengetahui ukuran, bentuk, dan lokasi elemen, tetapi Anda masih harus menilai dalam urutan apa Anda melukisnya.

Misalnya, z-index mungkin ditetapkan untuk elemen tertentu, dalam hal ini, menggambar dengan urutan elemen yang ditulis dalam HTML akan menyebabkan rendering yang salah.

indeks z gagal
Gambar 8: Elemen halaman yang muncul sesuai urutan markup HTML, sehingga menghasilkan gambar yang salah dirender karena indeks z tidak diperhitungkan

Pada langkah menggambar ini, thread utama akan menjalankan hierarki tata letak untuk membuat catatan paint. Paint record adalah catatan proses menggambar seperti "latar belakang terlebih dahulu, lalu teks, lalu persegi panjang". Jika Anda pernah menggambar pada elemen <canvas> menggunakan JavaScript, proses ini mungkin tidak asing bagi Anda.

cat catatan
Gambar 9: Thread utama yang berjalan melalui hierarki tata letak dan menghasilkan catatan paint

Memperbarui pipeline rendering membutuhkan biaya besar

Gambar 10: DOM+Gaya, Tata Letak, dan hierarki Paint sesuai urutan dihasilkan

Hal terpenting yang perlu dipahami dalam pipeline rendering adalah bahwa pada setiap langkah, hasil dari operasi sebelumnya digunakan untuk membuat data baru. Misalnya, jika ada perubahan pada hierarki tata letak, urutan Paint perlu dibuat ulang untuk bagian dokumen yang terpengaruh.

Jika Anda menganimasikan elemen, browser harus menjalankan operasi ini di antara setiap bingkai. Sebagian besar layar kami memuat ulang layar 60 kali per detik (60 fps); animasi akan tampak halus bagi mata manusia saat Anda memindahkan sesuatu di layar pada setiap frame. Namun, jika animasi kehilangan frame di antaranya, halaman akan terlihat "jank".

jage jank karena frame yang hilang
Gambar 11: Frame animasi pada linimasa

Meskipun operasi rendering Anda dapat mengimbangi refresh layar, penghitungan ini berjalan di thread utama, yang berarti dapat diblokir saat aplikasi Anda menjalankan JavaScript.

jage jank oleh JavaScript
Gambar 12: Frame animasi pada linimasa, tetapi satu frame diblokir oleh JavaScript

Anda dapat membagi operasi JavaScript menjadi beberapa bagian kecil dan menjadwalkan untuk berjalan di setiap frame menggunakan requestAnimationFrame(). Untuk mendapatkan informasi lebih lanjut tentang topik ini, lihat Mengoptimalkan Eksekusi JavaScript. Anda juga dapat menjalankan JavaScript di Web Workers untuk menghindari pemblokiran thread utama.

meminta bingkai animasi
Gambar 13: Potongan JavaScript yang lebih kecil yang berjalan di linimasa dengan frame animasi

Pengomposisian

Bagaimana Anda akan menggambar halaman?

Gambar 14: Animasi proses raster naif

Setelah browser mengetahui struktur dokumen, gaya setiap elemen, geometri halaman, dan urutan warna, bagaimana cara menggambar halaman? Mengubah informasi ini menjadi piksel pada layar disebut rasterisasi.

Mungkin cara yang naif untuk menangani hal ini adalah dengan melakukan raster bagian di dalam area pandang. Jika pengguna men-scroll halaman, pindahkan frame raster, dan isi bagian yang hilang dengan melakukan raster lagi. Inilah cara Chrome menangani rasterisasi saat pertama kali dirilis. Namun, browser modern menjalankan proses yang lebih canggih yang disebut komposisi.

Apa itu pengomposisian

Gambar 15: Animasi proses komposisi

Komposisi adalah teknik untuk memisahkan bagian halaman menjadi beberapa lapisan, merasterisasinya secara terpisah, dan menggabungkan sebagai halaman dalam thread terpisah yang disebut thread compositor. Jika scroll terjadi, karena lapisan sudah dirasterisasi, yang harus dilakukan hanyalah menggabungkan frame baru. Animasi dapat dilakukan dengan cara yang sama dengan memindahkan lapisan dan menggabungkan frame baru.

Anda dapat melihat bagaimana situs Anda dibagi menjadi beberapa lapisan di DevTools menggunakan panel Lapisan.

Membagi menjadi beberapa lapisan

Untuk mengetahui elemen mana yang perlu berada di lapisan mana, thread utama akan melewati hierarki tata letak untuk membuat hierarki lapisan (bagian ini disebut "Update Layer Tree" di panel performa DevTools). Jika bagian tertentu dari halaman yang seharusnya berupa lapisan terpisah (seperti menu sisi geser) tidak mendapatkan lapisan tersebut, Anda dapat menunjukkannya ke browser menggunakan atribut will-change di CSS.

pohon lapisan
Gambar 16: Thread utama yang berjalan melalui hierarki tata letak yang menghasilkan hierarki lapisan

Anda mungkin tergoda untuk memberikan lapisan ke setiap elemen, tetapi pengomposisian di seluruh lapisan yang berlebihan dapat menyebabkan operasi yang lebih lambat daripada meraster bagian-bagian kecil halaman setiap frame. Oleh karena itu, Anda harus mengukur performa rendering aplikasi. Untuk informasi lebih lanjut tentang topik, lihat Berpegang pada Properti Khusus Compositor dan Mengelola Jumlah Lapisan.

Raster dan komposit dari thread utama

Setelah hierarki lapisan dibuat dan urutan paint ditentukan, thread utama akan meng-commit informasi tersebut ke thread compositor. Thread compositor kemudian merasterisasi setiap lapisan. Lapisan dapat berukuran besar seperti keseluruhan panjang halaman sehingga thread compositor membaginya menjadi kartu dan mengirimkan setiap kartu ke thread raster. Thread raster melakukan rasterisasi setiap kartu dan menyimpannya dalam memori GPU.

raster
Gambar 17: Thread raster yang membuat bitmap ubin dan mengirimnya ke GPU

Thread compositor dapat memprioritaskan thread raster yang berbeda sehingga hal-hal dalam area pandang (atau di dekatnya) dapat diraster terlebih dahulu. Lapisan juga memiliki beberapa penyusunan ubin untuk resolusi yang berbeda guna menangani hal-hal seperti tindakan zoom.

Setelah kartu diraster, thread compositor akan mengumpulkan informasi kartu yang disebut persegi gambar untuk membuat frame compositor.

Menggambar segi empat Berisi informasi seperti lokasi kartu di memori dan tempat menggambar kartu di halaman dengan mempertimbangkan komposisi halaman.
Frame compositor Kumpulan persegi panjang gambar yang mewakili bingkai halaman.

Frame compositor kemudian dikirim ke proses browser melalui IPC. Pada tahap ini, frame compositor lain dapat ditambahkan dari UI thread untuk perubahan UI browser atau dari proses perender lain untuk ekstensi. Frame compositor ini dikirim ke GPU untuk menampilkannya di layar. Jika peristiwa scroll terjadi, thread compositor akan membuat frame compositor lain untuk dikirim ke GPU.

gabungan
Gambar 18: Thread kompositor yang membuat frame gabungan. Frame dikirim ke proses browser lalu ke GPU

Manfaat komposisi adalah dilakukan tanpa melibatkan thread utama. Thread compiler tidak perlu menunggu penghitungan gaya atau eksekusi JavaScript. Inilah sebabnya pengomposisian animasi saja dianggap terbaik untuk performa yang lancar. Jika tata letak atau paint perlu dihitung lagi, thread utama harus dilibatkan.

Rangkuman

Dalam postingan ini, kita melihat pipeline rendering dari penguraian hingga pengomposisian. Semoga, Anda sekarang diberdayakan untuk membaca lebih lanjut tentang pengoptimalan kinerja situs web.

Pada postingan berikutnya dan terakhir dari rangkaian ini, kita akan melihat thread compositor secara lebih detail dan melihat apa yang terjadi saat input pengguna seperti mouse move dan click masuk.

Apakah Anda menikmati postingan itu? Jika ada pertanyaan atau saran untuk postingan mendatang, saya ingin mendengar pendapat Anda di bagian komentar di bawah atau @kosamari di Twitter.

Berikutnya: Input masuk ke compositor