Saya Ian Kilpatrick, pemimpin engineering di tim tata letak Blink, bersama dengan Koji Ishii. Sebelum bekerja di tim Blink, saya adalah engineer frontend (sebelum Google memiliki peran "engineer frontend"), yang membuat fitur dalam Google Dokumen, Drive, dan Gmail. Setelah sekitar lima tahun dalam peran tersebut, saya mengambil risiko besar dengan beralih ke tim Blink, mempelajari C++ secara efektif saat bekerja, dan mencoba meningkatkan codebase Blink yang sangat kompleks. Bahkan hingga saat ini, saya hanya memahami sebagian kecil darinya. Saya berterima kasih atas waktu yang diberikan kepada saya selama periode ini. Saya merasa terhibur dengan kenyataan bahwa banyak "engineer front-end yang sedang pulih" melakukan transisi menjadi "engineer browser" sebelum saya.
Pengalaman saya sebelumnya telah membimbing saya secara pribadi saat berada di tim Blink. Sebagai engineer frontend, saya terus-menerus mengalami inkonsistensi browser, masalah performa, bug rendering, dan fitur yang tidak ada. LayoutNG adalah peluang bagi saya untuk membantu memperbaiki masalah ini secara sistematis dalam sistem tata letak Blink, dan mewakili jumlah upaya banyak engineer selama bertahun-tahun.
Dalam postingan ini, saya akan menjelaskan bagaimana perubahan arsitektur besar seperti ini dapat mengurangi dan memitigasi berbagai jenis bug dan masalah performa.
Tampilan arsitektur mesin tata letak dari ketinggian 9.144 meter
Sebelumnya, hierarki tata letak Blink adalah yang akan saya sebut sebagai "hierarki yang dapat diubah".
Setiap objek dalam hierarki tata letak berisi informasi input, seperti ukuran yang tersedia yang diterapkan oleh induk, posisi float, dan informasi output, misalnya, lebar dan tinggi akhir objek atau posisi x dan y-nya.
Objek ini disimpan di antara render. Saat perubahan gaya terjadi, kami menandai objek tersebut sebagai kotor dan begitu juga semua induknya dalam hierarki. Saat fase tata letak pipeline rendering dijalankan, kita akan membersihkan hierarki, menelusuri objek yang kotor, lalu menjalankan tata letak untuk membuatnya bersih.
Kami mendapati bahwa arsitektur ini menghasilkan banyak jenis masalah, yang akan kami jelaskan di bawah. Namun, pertama-tama, mari kita mundur sejenak dan pertimbangkan input dan output tata letak.
Menjalankan tata letak pada node dalam hierarki ini secara konseptual menggunakan "Gaya plus DOM", dan batasan induk apa pun dari sistem tata letak induk (petak, blok, atau fleksibel), menjalankan algoritma batasan tata letak, dan menghasilkan hasil.
Arsitektur baru kami merumuskan model konseptual ini. Kita masih memiliki hierarki tata letak, tetapi menggunakannya terutama untuk menyimpan input dan output tata letak. Untuk output, kita membuat objek yang tidak dapat diubah yang benar-benar baru yang disebut hierarki fragmen.
Saya telah membahas hierarki fragmen yang tidak dapat diubah sebelumnya, yang menjelaskan cara hierarki tersebut dirancang untuk menggunakan kembali sebagian besar hierarki sebelumnya untuk tata letak inkremental.
Selain itu, kita menyimpan objek batasan induk yang menghasilkan fragmen tersebut. Kita menggunakannya sebagai kunci cache yang akan kita bahas lebih lanjut di bawah.
Algoritma tata letak inline (teks) juga ditulis ulang agar cocok dengan arsitektur baru yang tidak dapat diubah. Versi ini tidak hanya menghasilkan representasi daftar datar yang tidak dapat diubah untuk tata letak inline, tetapi juga menampilkan cache tingkat paragraf untuk pengaturan ulang yang lebih cepat, bentuk per paragraf untuk menerapkan fitur font di seluruh elemen dan kata, algoritma dua arah Unicode baru menggunakan ICU, banyak perbaikan ketepatan, dan lainnya.
Jenis bug tata letak
Secara umum, bug tata letak terbagi menjadi empat kategori berbeda, masing-masing dengan penyebab utama yang berbeda.
Ketepatan
Saat memikirkan bug dalam sistem rendering, kita biasanya memikirkan kebenaran, misalnya: "Browser A memiliki perilaku X, sedangkan Browser B memiliki perilaku Y", atau "Browser A dan B rusak". Sebelumnya, inilah yang menghabiskan banyak waktu kita, dan dalam prosesnya, kita terus-menerus berurusan dengan sistem. Mode kegagalan yang umum adalah menerapkan perbaikan yang sangat ditargetkan untuk satu bug, tetapi beberapa minggu kemudian kami mendapati bahwa kami telah menyebabkan regresi di bagian lain sistem (yang tampaknya tidak terkait).
Seperti yang dijelaskan dalam postingan sebelumnya, hal ini adalah tanda sistem yang sangat rapuh. Khusus untuk tata letak, kami tidak memiliki kontrak yang jelas antara class apa pun, sehingga menyebabkan engineer browser bergantung pada status yang tidak seharusnya, atau salah menafsirkan beberapa nilai dari bagian lain sistem.
Misalnya, pada satu titik, kami memiliki rantai sekitar 10 bug selama lebih dari setahun, terkait tata letak fleksibel. Setiap perbaikan menyebabkan masalah akurasi atau performa di bagian sistem, yang menyebabkan bug lainnya.
Setelah LayoutNG menentukan kontrak antara semua komponen dalam sistem tata letak dengan jelas, kami mendapati bahwa kita dapat menerapkan perubahan dengan lebih percaya diri. Kami juga sangat diuntungkan dari project Pengujian Platform Web (WPT) yang sangat baik, yang memungkinkan beberapa pihak berkontribusi pada rangkaian pengujian web yang umum.
Saat ini, kami mendapati bahwa jika kami merilis regresi yang sebenarnya di saluran stabil, regressi tersebut biasanya tidak memiliki pengujian terkait di repositori WPT, dan tidak disebabkan oleh kesalahpahaman tentang kontrak komponen. Selain itu, sebagai bagian dari kebijakan perbaikan bug, kami selalu menambahkan pengujian WPT baru, yang membantu memastikan bahwa tidak ada browser yang akan melakukan kesalahan yang sama lagi.
Pembatalan validasi
Jika pernah mengalami bug misterius yang membuat bug hilang secara ajaib saat mengubah ukuran jendela browser atau mengalihkan properti CSS, Anda mengalami masalah pembatalan validasi yang tidak memadai. Secara efektif, sebagian hierarki yang dapat diubah dianggap bersih, tetapi karena beberapa perubahan pada batasan induk, hierarki tersebut tidak merepresentasikan output yang benar.
Hal ini sangat umum dengan mode tata letak dua langkah (melalui hierarki tata letak dua kali untuk menentukan status tata letak akhir) yang dijelaskan di bawah. Sebelumnya, kode kita akan terlihat seperti:
if (/* some very complicated statement */) {
child->ForceLayout();
}
Perbaikan untuk jenis bug ini biasanya adalah:
if (/* some very complicated statement */ ||
/* another very complicated statement */) {
child->ForceLayout();
}
Perbaikan untuk jenis masalah ini biasanya akan menyebabkan regresi performa yang parah, (lihat pembatalan validasi berlebih di bawah), dan sangat sulit untuk diperbaiki.
Saat ini (seperti yang dijelaskan di atas), kita memiliki objek batasan induk yang tidak dapat diubah yang menjelaskan semua input dari tata letak induk ke turunan. Kita menyimpannya dengan fragmen yang tidak dapat diubah. Oleh karena itu, kami memiliki tempat terpusat tempat kami diff kedua input ini untuk menentukan apakah turunan perlu melakukan penerusan tata letak lain. Logika perbedaan ini rumit, tetapi terkandung dengan baik. Men-debug class masalah pembatalan validasi ini biasanya akan memeriksa dua input secara manual dan memutuskan apa yang berubah dalam input sehingga diperlukan penerusan tata letak lain.
Perbaikan pada kode perbedaan ini biasanya sederhana, dan mudah diuji unit karena kemudahan pembuatan objek independen ini.
Kode perbedaan untuk contoh di atas adalah:
if (width.IsPercent()) {
if (old_constraints.WidthPercentageSize()
!= new_constraints.WidthPercentageSize())
return kNeedsLayout;
}
if (height.IsPercent()) {
if (old_constraints.HeightPercentageSize()
!= new_constraints.HeightPercentageSize())
return kNeedsLayout;
}
Histerisis
Jenis bug ini mirip dengan pembatalan validasi yang tidak memadai. Pada dasarnya, dalam sistem sebelumnya, sangat sulit untuk memastikan bahwa tata letak bersifat idempoten, yaitu menjalankan ulang tata letak dengan input yang sama, menghasilkan output yang sama.
Dalam contoh di bawah, kita hanya mengganti properti CSS bolak-balik antara dua nilai. Namun, hal ini akan menghasilkan persegi panjang yang "bertambah tanpa batas".
Dengan hierarki yang dapat diubah sebelumnya, sangat mudah untuk memasukkan bug seperti ini. Jika kode membuat kesalahan saat membaca ukuran atau posisi objek pada waktu atau tahap yang salah (misalnya, kita tidak "menghapus" ukuran atau posisi sebelumnya), kita akan segera menambahkan bug histeresis yang halus. Bug ini biasanya tidak muncul dalam pengujian karena sebagian besar pengujian berfokus pada satu tata letak dan rendering. Yang lebih mengkhawatirkan, kami tahu bahwa beberapa histerisis ini diperlukan agar beberapa mode tata letak berfungsi dengan benar. Kami mengalami bug saat melakukan pengoptimalan untuk menghapus penerusan tata letak, tetapi menyebabkan "bug" karena mode tata letak memerlukan dua penerusan untuk mendapatkan output yang benar.
Dengan LayoutNG, karena kita memiliki struktur data input dan output eksplisit, dan mengakses status sebelumnya tidak diizinkan, kita telah memitigasi class bug ini secara luas dari sistem tata letak.
Over-invalidation dan performa
Hal ini merupakan kebalikan langsung dari class bug under-invalidation. Sering kali saat memperbaiki bug pembatalan validasi, kami akan memicu penurunan performa.
Kami sering kali harus membuat pilihan sulit yang lebih mengutamakan ketepatan daripada performa. Di bagian berikutnya, kita akan membahas lebih lanjut cara kami memitigasi jenis masalah performa ini.
Munculnya tata letak dua langkah dan penurunan performa
Tata letak fleksibel dan petak mewakili pergeseran dalam ekspresi tata letak di web. Namun, algoritma ini pada dasarnya berbeda dengan algoritma tata letak blok yang ada sebelumnya.
Tata letak blok (hampir dalam semua kasus) hanya mengharuskan mesin untuk melakukan tata letak pada semua turunannya tepat sekali. Hal ini bagus untuk performa, tetapi pada akhirnya tidak seekspresif yang diinginkan developer web.
Misalnya, sering kali Anda ingin ukuran semua turunan diperluas ke ukuran terbesar. Untuk mendukung hal ini, tata letak induk (flex atau petak) akan melakukan tahap pengukuran untuk menentukan ukuran setiap turunan, lalu tahap tata letak untuk meregangkan semua turunan ke ukuran ini. Perilaku ini adalah default untuk tata letak flex dan petak.
Tata letak dua langkah ini awalnya dapat diterima dari segi performa, karena orang biasanya tidak menyusunnya secara mendalam. Namun, kami mulai melihat masalah performa yang signifikan seiring munculnya konten yang lebih kompleks. Jika Anda tidak menyimpan hasil fase pengukuran dalam cache, hierarki tata letak akan beralih antara status measure dan status layout akhir.
Sebelumnya, kita akan mencoba menambahkan cache yang sangat spesifik ke tata letak fleksibel dan petak untuk mengatasi jenis penurunan performa ini. Cara ini berhasil (dan kami sudah sangat jauh dengan Flex), tetapi terus-menerus mengalami bug pembatalan validasi yang kurang dan berlebih.
LayoutNG memungkinkan kita membuat struktur data eksplisit untuk input dan output tata letak, dan selain itu, kita telah membuat cache pengukuran dan penerusan tata letak. Hal ini mengembalikan kompleksitas ke O(n), sehingga menghasilkan performa linear yang dapat diprediksi untuk developer web. Jika ada kasus saat tata letak melakukan tata letak tiga kali, kita juga akan meng-cache kartu tersebut. Hal ini dapat membuka peluang untuk memperkenalkan mode tata letak yang lebih canggih dengan aman pada masa mendatang. Ini adalah contoh bagaimana RenderingNG secara fundamental membuka ekstensi secara menyeluruh. Dalam beberapa kasus, tata letak Petak dapat memerlukan tata letak tiga langkah, tetapi saat ini sangat jarang terjadi.
Kami mendapati bahwa ketika developer mengalami masalah performa, terutama terkait tata letak, hal ini biasanya disebabkan oleh bug waktu tata letak eksponensial, bukan throughput mentah dari tahap tata letak pipeline. Jika perubahan inkremental kecil (satu elemen yang mengubah satu properti css) menghasilkan tata letak 50-100 md, hal ini mungkin merupakan bug tata letak eksponensial.
Ringkasan
Tata letak adalah area yang sangat kompleks, dan kita tidak membahas semua detail menarik seperti pengoptimalan tata letak inline (benar-benar cara kerja seluruh sub-sistem inline dan teks), dan bahkan konsep yang dibahas di sini hanya sekilas, dan mengabaikan banyak detail. Namun, semoga kami telah menunjukkan bagaimana meningkatkan arsitektur sistem secara sistematis dapat menghasilkan keuntungan yang luar biasa dalam jangka panjang.
Meskipun demikian, kami tahu masih ada banyak pekerjaan yang harus dilakukan. Kami mengetahui jenis masalah (baik performa maupun ketepatan) yang sedang kami pecahkan, dan sangat antusias dengan fitur tata letak baru yang akan hadir di CSS. Kami yakin arsitektur LayoutNG membuat penyelesaian masalah ini aman dan mudah ditangani.
Satu gambar (Anda tahu gambar mana!) oleh Una Kravets.