Tampilan mendalam pada browser web modern (bagian 3)

Mariko Kosaka

Cara Kerja Bagian Dalam Proses Perender

Artikel 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 mempelajari lebih lanjut, bagian Performa pada 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 dari JavaScript Anda ditangani oleh thread pekerja jika Anda menggunakan pekerja web atau pekerja layanan. Thread Compositor dan raster juga berjalan 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 dalam

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 dalam DOM ditentukan oleh Standar HTML. Anda mungkin telah memperhatikan bahwa memasukkan HTML ke browser tidak pernah menampilkan 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) diperlakukan 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 hal-hal ini dilakukan, Anda dapat membaca bagian "Pengantar penanganan error dan kasus aneh di parser" pada spesifikasi HTML.

Memuat subresource

{i>Website<i} biasanya menggunakan sumber daya eksternal seperti gambar, CSS, dan JavaScript. File-file tersebut perlu dimuat dari jaringan atau cache. Thread utama dapat memintanya satu per satu saat menemukannya saat mengurai untuk membangun DOM, tetapi untuk mempercepat, "pemindai pramuat" dijalankan secara serentak. Jika ada hal seperti <img> atau <link> di 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 menguraikan HTML dan membuat hierarki DOM

JavaScript dapat memblokir penguraian

Saat menemukan tag <script>, parser HTML akan menjeda penguraian dokumen HTML dan harus memuat, mengurai, dan mengeksekusi kode JavaScript. Mengapa? karena JavaScript dapat mengubah bentuk dokumen menggunakan hal-hal seperti document.write() yang mengubah seluruh struktur DOM (ringkasan model penguraian dalam spesifikasi HTML memiliki diagram yang bagus). Inilah alasan parser HTML harus menunggu JavaScript berjalan sebelum dapat melanjutkan penguraian dokumen HTML. Jika Anda ingin mengetahui apa yang terjadi dalam eksekusi JavaScript, tim V8 mengadakan diskusi dan postingan blog mengenai hal ini.

Petunjuk ke browser cara Anda ingin memuat resource

Ada banyak cara yang dapat digunakan developer web untuk 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 akan 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 benar-benar diperlukan untuk navigasi saat ini dan Anda ingin mendownloadnya sesegera mungkin. Anda dapat membaca selengkapnya tentang hal ini di Prioritrasi Resource – Mendapatkan Browser untuk Membantu Anda.

Penghitungan gaya

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

Gaya yang dihitung
Gambar 3: Thread utama mengurai CSS untuk menambahkan gaya terkomputasi

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

Tata Letak

Sekarang proses perender mengetahui struktur dokumen dan gaya untuk setiap node, tetapi hal tersebut tidak cukup untuk merender halaman. Bayangkan Anda sedang mencoba mendeskripsikan sebuah lukisan kepada teman Anda melalui telepon. "Ada lingkaran merah besar dan kotak biru kecil" tidak memberikan informasi yang cukup bagi teman Anda untuk mengetahui seperti apa tampilan lukisan itu.

game mesin faks manusia
Gambar 4: Seseorang berdiri di depan lukisan, saluran telepon yang terhubung ke orang lain

Tata letak adalah proses untuk menemukan geometri elemen. Thread utama berjalan melalui DOM dan gaya terkomputasi, lalu membuat hierarki tata letak yang memiliki informasi seperti koordinat x y dan ukuran kotak pembatas. Hierarki tata letak mungkin mirip dengan struktur hierarki DOM, tetapi hanya berisi informasi yang terkait dengan yang terlihat di halaman. Jika display: none diterapkan, elemen tersebut bukan bagian dari hierarki tata letak (namun, elemen dengan visibility: hidden ada dalam hierarki tata letak). Demikian pula, jika elemen pseudo dengan konten seperti p::before{content:"Hi!"} diterapkan, elemen 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 bergerak karena perubahan jeda baris

Menentukan Tata Letak halaman adalah tugas yang menantang. Bahkan tata letak halaman yang paling sederhana seperti blok yang mengalir dari atas ke bawah juga harus mempertimbangkan seberapa besar font dan di mana harus memisahkan baris karena hal itu memengaruhi ukuran dan bentuk paragraf; yang kemudian memengaruhi posisi paragraf berikutnya.

CSS dapat membuat elemen mengapung ke satu sisi, menyamarkan item tambahan, dan mengubah arah penulisan. Bisa Anda bayangkan, tahap tata letak ini memiliki tugas yang luar biasa. Di Chrome, seluruh tim insinyur mengerjakan tata letak. Jika Anda ingin melihat detail pekerjaan mereka, beberapa diskusi dari BlinkOn Conference akan direkam dan cukup menarik untuk ditonton.

Cat

game menggambar
Gambar 7: Seseorang di depan kanvas memegang kuas, ingin tahu apakah ia harus menggambar lingkaran terlebih dahulu atau persegi terlebih dahulu

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

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

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

Pada langkah paint ini, thread utama akan menelusuri hierarki tata letak untuk membuat kumpulan data cat. Catatan cat adalah catatan proses pengecatan seperti "latar belakang terlebih dahulu, lalu teks, lalu persegi panjang". Jika Anda telah menggambar pada elemen <canvas> menggunakan JavaScript, proses ini mungkin tidak asing bagi Anda.

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

Mengupdate pipeline rendering memerlukan banyak biaya

Gambar 10: hierarki DOM+Gaya, Tata Letak, dan Menggambar secara berurutan

Hal terpenting yang perlu dipahami dalam pipeline rendering adalah pada setiap langkah, hasil dari operasi sebelumnya akan digunakan untuk membuat data baru. Misalnya, jika ada perubahan di hierarki tata letak, urutan Paint harus 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 di mata manusia saat Anda menggerakkan objek di layar pada setiap frame. Namun, jika animasi melewatkan frame di antaranya, halaman akan muncul "jank".

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

Meskipun operasi rendering Anda mengikuti muat ulang layar, penghitungan ini berjalan di thread utama, yang berarti penghitungan ini 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 potongan-potongan kecil dan jadwal untuk dijalankan di setiap frame menggunakan requestAnimationFrame(). Untuk informasi selengkapnya tentang topik ini, lihat Mengoptimalkan Eksekusi JavaScript . Anda juga dapat menjalankan JavaScript di Web Worker untuk menghindari pemblokiran thread utama.

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

Pengomposisian

Bagaimana Anda akan menggambar sebuah halaman?

Gambar 14: Animasi proses raster yang naif

Setelah browser mengetahui struktur dokumen, gaya setiap elemen, geometri halaman, dan urutan cat, bagaimana cara menggambar halaman? Mengubah informasi ini menjadi {i>pixel<i} di layar disebut rasterisasi.

Mungkin cara naif untuk menangani ini adalah dengan melakukan raster pada bagian di dalam area pandang. Jika pengguna men-scroll halaman, lalu memindahkan frame raster, dan mengisi bagian yang hilang dengan melakukan raster lagi. Berikut adalah cara Chrome menangani proses raster saat pertama kali dirilis. Namun, browser modern menjalankan proses yang lebih canggih yang disebut pengomposisian.

Apa itu pengomposisian

Gambar 15: Animasi proses pengomposisian

Pengomposisian adalah teknik untuk memisahkan bagian halaman menjadi lapisan, merasternya secara terpisah, dan menggabungkan sebagai halaman dalam thread terpisah yang disebut thread compositor. Jika scroll terjadi, karena lapisan sudah diraster, yang perlu dilakukan hanyalah menyusun frame baru. Animasi dapat dilakukan dengan cara yang sama dengan memindahkan lapisan dan menyusun frame baru.

Anda dapat melihat bagaimana situs 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 berjalan melalui 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 samping slide-in) tidak mendapatkannya, Anda dapat memberi petunjuk ke browser dengan 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 antara lapisan yang berlebihan dapat menyebabkan operasi yang lebih lambat daripada meraster bagian kecil dari setiap frame. Oleh karena itu, penting bagi Anda untuk mengukur performa rendering aplikasi. Untuk informasi selengkapnya tentang topik ini, lihat Berpegang pada Properti Khusus Compositor dan Mengelola Jumlah Lapisan.

Raster dan komposit dari thread utama

Setelah hierarki lapisan dibuat dan urutan pewarnaan ditentukan, thread utama meng-commit informasi tersebut ke thread compositor. Thread compositor kemudian merasterisasi setiap lapisan. Lapisan bisa berukuran besar seperti keseluruhan panjang halaman, sehingga thread compositor membaginya menjadi kartu dan mengirimkan setiap kartu ke thread raster. Thread raster me-raster setiap kartu dan menyimpannya di memori GPU.

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

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

Setelah kartu diraster, thread compositor mengumpulkan informasi kartu yang disebut gambar segi empat untuk membuat frame compositor.

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

Sebuah {i>frame<i} compositor kemudian dikirimkan ke proses {i>browser<i} melalui IPC. Pada tahap ini, frame compositor lain dapat ditambahkan dari UI thread untuk perubahan UI browser atau dari proses perender lainnya untuk ekstensi. Frame compositor ini dikirim ke GPU untuk menampilkannya di layar. Jika peristiwa scroll masuk, thread compositor akan membuat frame compositor lain untuk dikirim ke GPU.

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

Manfaat pengomposisian adalah dapat dilakukan tanpa melibatkan thread utama. Thread Compositor tidak perlu menunggu penghitungan gaya atau eksekusi JavaScript. Inilah alasan mengomposisikan animasi saja dianggap paling baik untuk performa yang lancar. Jika tata letak atau paint perlu dihitung lagi, thread utama harus dilibatkan.

Penutup

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

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

Apakah Anda menyukai postingan ini? Jika ada pertanyaan atau saran untuk postingan mendatang, kami ingin mendengar pendapat Anda di bagian komentar di bawah atau @kosamari di Twitter.

Berikutnya: Input akan dikirimkan ke compositor