Tampilan mendalam pada browser web modern (bagian 3)

Mariko Kosaka

Cara kerja internal Proses Perender

Artikel ini adalah bagian 3 dari seri blog 4 bagian 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 di Dasar-Dasar Web memiliki lebih banyak referensi.

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 Anda ditangani oleh thread pekerja jika Anda menggunakan pekerja web atau pekerja layanan. Thread raster dan kompositor 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 digunakan pengguna untuk berinteraksi.

Proses perender
Gambar 1: Proses perender dengan thread utama, thread pekerja, thread kompositor, dan raster thread di dalam

Penguraian

Pembuatan DOM

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

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

Mengurai dokumen HTML menjadi 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 dirancang untuk menangani error tersebut dengan baik. Jika ingin mengetahui cara melakukannya, Anda dapat membaca bagian "Pengantar penanganan error dan kasus aneh dalam parser" di spesifikasi HTML.

Pemuatan 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 saat menemukannya saat mengurai untuk membuat DOM, tetapi untuk mempercepat, "pemindai pramuat" dijalankan secara serentak. Jika ada hal-hal seperti <img> atau <link> dalam dokumen HTML, pemindai pramuat akan melihat token yang dihasilkan oleh parser HTML dan mengirim permintaan ke thread jaringan dalam proses browser.

DOM
Gambar 2: Thread utama 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, 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 sebabnya parser HTML harus menunggu JavaScript berjalan sebelum dapat melanjutkan penguraian dokumen HTML. Jika Anda ingin tahu apa yang terjadi dalam eksekusi JavaScript, tim V8 memiliki presentasi dan postingan blog tentang hal ini.

Memberi petunjuk kepada browser tentang cara Anda ingin memuat resource

Ada banyak cara yang dapat dilakukan developer web untuk mengirim petunjuk ke browser agar memuat resource dengan baik. Jika JavaScript tidak menggunakan document.write(), Anda dapat menambahkan atribut async atau defer ke tag <script>. Kemudian, 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 tersebut sangat diperlukan untuk navigasi saat ini dan Anda ingin mendownloadnya sesegera mungkin. Anda dapat membaca selengkapnya tentang hal ini di Prioritas Aset – Membuat Browser Membantu Anda.

Penghitungan gaya

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

Gaya yang dikomputasi
Gambar 3: Thread utama menguraikan CSS untuk menambahkan gaya yang dihitung

Meskipun Anda tidak menyediakan CSS, setiap node DOM memiliki gaya yang dihitung. Tag <h1> ditampilkan lebih besar dari tag <h2> dan margin ditentukan untuk setiap elemen. Hal ini karena browser memiliki lembar gaya 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 hal itu tidak cukup untuk merender halaman. Bayangkan Anda mencoba menjelaskan sebuah lukisan kepada teman melalui ponsel. "Ada lingkaran merah besar dan kotak biru kecil" bukanlah informasi yang cukup bagi teman Anda untuk mengetahui seperti apa lukisan itu.

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

Tata letak adalah proses untuk menemukan geometri elemen. Thread utama menelusuri DOM dan gaya yang dihitung serta membuat hierarki tata letak yang memiliki informasi seperti koordinat x y dan ukuran kotak pembatas. Hierarki tata letak mungkin memiliki struktur yang 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 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 di DOM.

tata letak
Gambar 5: Thread utama yang memeriksa hierarki DOM dengan gaya yang dikomputasi dan menghasilkan hierarki tata letak
Gambar 6: Tata letak kotak untuk paragraf yang berpindah karena perubahan jeda baris

Menentukan Tata letak halaman adalah tugas yang menantang. Bahkan tata letak halaman yang paling sederhana seperti alur blok dari atas ke bawah harus mempertimbangkan ukuran font dan tempat untuk membuat baris baru karena hal tersebut memengaruhi ukuran dan bentuk paragraf; yang kemudian memengaruhi tempat paragraf berikutnya harus berada.

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

Cat

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

Memiliki DOM, gaya, dan tata letak masih belum cukup untuk merender halaman. Misalnya, Anda mencoba mereproduksi lukisan. Anda tahu ukuran, bentuk, dan lokasi elemen, tetapi Anda masih harus menilai urutan untuk melukisnya.

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

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

Pada langkah gambar ini, thread utama menelusuri hierarki tata letak untuk membuat data gambar. Data 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 sudah tidak asing bagi Anda.

data cat
Gambar 9: Thread utama yang menelusuri hierarki tata letak dan menghasilkan data cat

Memperbarui pipeline rendering membutuhkan biaya yang mahal

Gambar 10: Hierarki DOM+Gaya, Tata Letak, dan Gambar sesuai urutan pembuatannya

Hal terpenting yang harus dipahami dalam pipeline rendering adalah pada setiap langkah, hasil operasi sebelumnya digunakan untuk membuat data baru. Misalnya, jika ada perubahan dalam 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 frame. 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 melewatkan frame di antaranya, halaman akan tampak "janggal".

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

Meskipun operasi rendering Anda mengikuti 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 dijalankan di setiap frame menggunakan requestAnimationFrame(). Untuk mengetahui informasi selengkapnya tentang topik ini, lihat Mengoptimalkan Eksekusi JavaScript . Anda juga dapat menjalankan JavaScript di Web Worker untuk menghindari pemblokiran thread utama.

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

Komposisi

Bagaimana cara Anda menggambar halaman?

Gambar 14: Animasi proses rasterisasi naif

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

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

Apa yang dimaksud dengan komposisi

Gambar 15: Animasi proses komposisi

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

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

Membagi menjadi beberapa lapisan

Untuk mengetahui elemen mana yang harus berada di lapisan mana, thread utama akan menelusuri hierarki tata letak untuk membuat hierarki lapisan (bagian ini disebut "Update Layer Tree" di panel performa DevTools). Jika bagian tertentu dari halaman yang seharusnya merupakan lapisan terpisah (seperti menu samping slide-in) tidak mendapatkannya, Anda dapat memberikan petunjuk ke browser menggunakan atribut will-change di CSS.

hierarki lapisan
Gambar 16: Thread utama yang menelusuri hierarki tata letak yang menghasilkan hierarki lapisan

Anda mungkin tergoda untuk memberikan lapisan ke setiap elemen, tetapi pengomposisian di seluruh lapisan yang berlebih dapat menyebabkan operasi yang lebih lambat daripada merasterisasi bagian kecil halaman setiap frame, sehingga Anda harus mengukur performa rendering aplikasi. Untuk mengetahui informasi selengkapnya tentang topik ini, lihat Menggunakan Properti Khusus Compositor dan Mengelola Jumlah Lapisan.

Raster dan gabungan dari thread utama

Setelah hierarki lapisan dibuat dan urutan gambar ditentukan, thread utama akan meng-commit informasi tersebut ke thread kompositor. Thread kompositor kemudian merasterisasi setiap lapisan. Lapisan dapat berukuran besar seperti seluruh panjang halaman, sehingga thread kompositor membaginya menjadi ubin dan mengirim setiap ubin ke thread raster. Thread raster merasterisasi setiap ubin dan menyimpannya di memori GPU.

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

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

Setelah ubin diraster, thread komposer mengumpulkan informasi ubin yang disebut draw quads untuk membuat frame komposer.

Menggambar kuadrat Berisi informasi seperti lokasi kartu di memori dan tempat di halaman untuk menggambar kartu dengan mempertimbangkan komposisi halaman.
Frame kompositor Kumpulan kuad gambar yang mewakili bingkai halaman.

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

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

Manfaat komposisi adalah dilakukan tanpa melibatkan thread utama. Thread komposer tidak perlu menunggu penghitungan gaya atau eksekusi JavaScript. Itulah sebabnya animasi khusus komposisi dianggap yang terbaik untuk performa yang lancar. Jika tata letak atau gambar perlu dihitung lagi, thread utama harus terlibat.

Penutup

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

Dalam postingan berikutnya dan terakhir dari seri ini, kita akan melihat thread kompositor secara lebih mendetail 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 masuk ke komposer