Tarayıcılar nasıl çalışır?

Modern web tarayıcıların perde arkası

Önsöz

WebKit ve Gecko'nun dahili işlemleriyle ilgili bu kapsamlı giriş makalesi, İsrailli geliştirici Tali Garsiel tarafından yapılan kapsamlı bir araştırmanın sonucudur. Birkaç yıl boyunca tarayıcı iç işleyişiyle ilgili yayınlanan tüm verileri inceledi ve web tarayıcısı kaynak kodunu okumak için çok zaman harcadı. Şunu yazdı:

Web geliştiricisi olarak tarayıcı işlemlerinin iç işleyişini öğrenmek, daha iyi kararlar almanıza ve geliştirmeyle ilgili en iyi uygulamaların nedenlerini anlamanıza yardımcı olur. Bu doküman oldukça uzun olsa da biraz zaman ayırıp incelemenizi öneririz. Bunu yaptığınızdan memnun olacaksınız.

Paul Irish, Chrome Geliştirici İlişkileri

Giriş

Web tarayıcıları en yaygın kullanılan yazılımlardır. Bu giriş niteliğindeki makalede, bu araçların perde arkasında nasıl çalıştığını açıklayacağım. Tarayıcı ekranında Google sayfasını görene kadar adres çubuğuna google.com yazdığınızda ne olacağını göreceğiz.

Konuşacağımız tarayıcılar

Günümüzde masaüstünde kullanılan beş ana tarayıcı vardır: Chrome, Internet Explorer, Firefox, Safari ve Opera. Mobil cihazlarda kullanılan başlıca tarayıcılar Android Tarayıcı, iPhone, Opera Mini ve Opera Mobile, UC Tarayıcı, Nokia S40/S60 tarayıcıları ve Chrome'dur. Opera tarayıcıları hariç bunların tümü WebKit'e dayanır. Açık kaynaklı Firefox ve Chrome tarayıcılarından ve kısmen açık kaynaklı Safari'den örnekler vereceğim. StatCounter istatistiklerine (Haziran 2013 itibarıyla) göre Chrome, Firefox ve Safari, dünya genelindeki masaüstü tarayıcı kullanımının yaklaşık% 71'ini oluşturuyor. Mobil cihazlarda Android Tarayıcı, iPhone ve Chrome'un kullanımının yaklaşık% 54'ü bu tarayıcılara aittir.

Tarayıcının ana işlevi

Bir tarayıcının ana işlevi, seçtiğiniz web kaynağını sunucudan isteyip tarayıcı penceresinde göstermektir. Kaynak genellikle bir HTML belgesidir ancak PDF, resim veya başka bir içerik türü de olabilir. Kaynağın konumu, kullanıcı tarafından URI (Tekdüzen Kaynak Tanımlayıcısı) kullanılarak belirtilir.

Tarayıcının HTML dosyalarını yorumlama ve görüntüleme şekli, HTML ve CSS spesifikasyonlarında belirtilir. Bu spesifikasyonlar, web'in standartlar kuruluşu olan W3C (World Wide Web Consortium) tarafından yönetilir. Tarayıcılar yıllarca spesifikasyonların yalnızca bir kısmına uyuyor ve kendi uzantılarını geliştiriyordu. Bu durum, web yazarları için ciddi uyumluluk sorunlarına neden oldu. Günümüzde çoğu tarayıcı bu spesifikasyonlara az çok uygundur.

Tarayıcı kullanıcı arayüzlerinin birbirleriyle çok ortak noktası vardır. Yaygın kullanıcı arayüzü öğeleri arasında şunlar yer alır:

  1. URI eklemek için adres çubuğu
  2. Geri ve ileri düğmeleri
  3. Yer işareti seçenekleri
  4. Mevcut dokümanların yenilenmesi veya yüklemesinin durdurulması için yenile ve durdur düğmeleri
  5. Ana sayfanıza götüren ana sayfa düğmesi

Tuhaf bir şekilde, tarayıcının kullanıcı arayüzü herhangi bir resmi spesifikasyonda belirtilmemiştir. Bu arayüz, yıllar süren deneyimler ve tarayıcıların birbirini taklit etmesi sonucunda ortaya çıkan iyi uygulamalardan oluşur. HTML5 spesifikasyonu, bir tarayıcıda bulunması gereken kullanıcı arayüzü öğelerini tanımlamaz ancak bazı yaygın öğeleri listeler. Bunlar arasında adres çubuğu, durum çubuğu ve araç çubuğu bulunur. Elbette Firefox'un indirme yöneticisi gibi belirli bir tarayıcıya özgü özellikler de vardır.

Üst düzey altyapı

Tarayıcının ana bileşenleri şunlardır:

  1. Kullanıcı arayüzü: Adres çubuğu, geri/ileri düğmesi, yer işareti menüsü vb. istenen sayfayı gördüğünüz pencere hariç tarayıcı ekranının her bölümü.
  2. Tarayıcı motoru: Kullanıcı arayüzü ile oluşturma motoru arasındaki işlemleri düzenler.
  3. Oluşturma motoru: İstenen içeriği göstermekten sorumludur. Örneğin, istenen içerik HTML ise oluşturma motoru HTML ve CSS'yi ayrıştırır ve ayrıştırılan içeriği ekranda gösterir.
  4. : Platformdan bağımsız bir arayüzün arkasında farklı platformlar için farklı uygulamalar kullanılarak HTTP istekleri gibi ağ çağrıları için.
  5. Kullanıcı arayüzü arka ucu: Açılır listeler ve pencereler gibi temel widget'ları çizmek için kullanılır. Bu arka uç, platforma özgü olmayan genel bir arayüz sunar. Alt katmanlarda işletim sistemi kullanıcı arayüzü yöntemleri kullanılır.
  6. JavaScript yorumlayıcısı. JavaScript kodunu ayrıştırmak ve yürütmek için kullanılır.
  7. Veri depolama. Bu, bir kalıcılık katmanıdır. Tarayıcının çerezler gibi her türlü veriyi yerel olarak kaydetmesi gerekebilir. Tarayıcılar, localStorage, IndexedDB, WebSQL ve FileSystem gibi depolama mekanizmalarını da destekler.
Tarayıcı bileşenleri
Şekil 1: Tarayıcı bileşenleri

Chrome gibi tarayıcıların, her sekme için bir tane olmak üzere birden fazla oluşturma motoru örneği çalıştırdığını unutmayın. Her sekme ayrı bir işlemde çalışır.

Oluşturma motorları

Oluşturma motorunun sorumluluğu, istenen içeriklerin tarayıcı ekranında gösterilmesidir.

Oluşturma motoru varsayılan olarak HTML ve XML dokümanlarını ve resimlerini görüntüleyebilir. Eklentiler veya uzantılar aracılığıyla diğer veri türlerini de görüntüleyebilir. Örneğin, PDF görüntüleyici eklentisi kullanarak PDF belgelerini görüntüleyebilir. Ancak bu bölümde ana kullanım alanına odaklanacağız: CSS kullanılarak biçimlendirilmiş HTML ve resimleri görüntüleme.

Farklı tarayıcılar farklı oluşturma motorları kullanır: Internet Explorer Trident, Firefox Gecko, Safari WebKit kullanır. Chrome ve Opera (15 sürümünden itibaren), WebKit'in bir çatalı olan Blink'i kullanır.

WebKit, Linux platformu için bir motor olarak başlayan ve Apple tarafından Mac ile Windows'u desteklemek için değiştirilen açık kaynak bir oluşturma motorudur.

Ana akış

Oluşturma motoru, istenen dokümanın içeriğini ağ katmanından almaya başlar. Bu işlem genellikle 8 KB'lık parçalar halinde yapılır.

Ardından, oluşturma motorunun temel akışı şu şekildedir:

Oluşturma motorunun temel akışı
Şekil 2: Oluşturma motorunun temel akışı

Oluşturma motoru, HTML dokümanını ayrıştırmaya başlar ve öğeleri "içerik ağacı" adı verilen bir ağaçtaki DOM düğümlerine dönüştürür. Motor, hem harici CSS dosyalarındaki hem de stil öğelerindeki stil verilerini ayrıştırır. HTML'deki görsel talimatlarla birlikte stil bilgileri, başka bir ağaç oluşturmak için kullanılır: oluşturma ağacı.

Oluşturma ağacı, renk ve boyutlar gibi görsel özelliklere sahip dikdörtgenler içerir. Dikdörtgenler ekranda gösterilecek şekilde doğru sıradadır.

Oluşturma ağacı oluşturulduktan sonra "düzenleme" işlemine girer. Yani her düğüme ekranda görünmesi gereken tam koordinatları vermeniz gerekir. Sonraki aşama boyama işlemidir. Oluşturma ağacı taranacak ve her düğüm, kullanıcı arayüzü arka uç katmanı kullanılarak boyanacaktır.

Bu sürecin kademeli olduğunu anlamanız önemlidir. Oluşturma motoru, daha iyi bir kullanıcı deneyimi için içeriği mümkün olan en kısa sürede ekranda göstermeye çalışır. Oluşturma ağacını oluşturmaya ve biçimlendirmeye başlamadan önce tüm HTML'nin ayrıştırılmasını beklemez. İçeriğin bazı bölümleri ayrıştırılıp gösterilirken işlem, ağdan gelen diğer içeriklerle devam eder.

Ana akış örnekleri

WebKit ana akışı.
Şekil 3: WebKit ana akışı
Mozilla'nın Gecko oluşturma motorunun ana akışı.
Şekil 4: Mozilla'nın Gecko oluşturma motorunun ana akışı

3. ve 4. resimlerde, WebKit ve Gecko'nun biraz farklı terminolojiler kullanmasına rağmen akışın temelde aynı olduğunu görebilirsiniz.

Gecko, görsel olarak biçimlendirilmiş öğelerin ağacını "Çerçeve ağacı" olarak adlandırır. Her öğe bir çerçevedir. WebKit, "Oluşturma Ağacı" terimini kullanır ve "Oluşturma Nesneleri"nden oluşur. WebKit, öğelerin yerleştirilmesi için "düzenleme" terimini kullanırken Gecko bunu "yeniden akış" olarak adlandırır. "Ek", WebKit'in oluşturma ağacı oluşturmak için DOM düğümlerini ve görsel bilgileri bağlamak için kullandığı terimdir. Semantik olmayan küçük bir fark, Gecko'nun HTML ile DOM ağacı arasında ekstra bir katmana sahip olmasıdır. "İçerik havuzu" olarak adlandırılan bu öğe, DOM öğeleri oluşturmaya yönelik bir fabrikadır. Akıştaki her bir bölümden bahsedeceğiz:

Ayrıştırma - genel

Ayrıştırma, oluşturma motorunda çok önemli bir işlem olduğundan bu konuyu biraz daha ayrıntılı olarak ele alacağız. Ayrıştırma hakkında kısa bir girişle başlayalım.

Bir dokümanı ayrıştırmak, dokümanı kodun kullanabileceği bir yapıya çevirmektir. Ayrıştırmanın sonucu genellikle dokümanın yapısını temsil eden bir düğüm ağacıdır. Buna ayrıştırma ağacı veya söz dizimi ağacı denir.

Örneğin, 2 + 3 - 1 ifadesi ayrıştırıldığında şu ağaç döndürülebilir:

Matematiksel ifade ağacı düğümü.
Şekil 5: matematiksel ifade ağaç düğümü

Dilbilgisi

Ayrıştırma işlemi, belgenin uyduğu söz dizimi kurallarına (yazıldığı dil veya biçim) dayanır. Ayrıştırabileceğiniz her biçimin, kelime hazinesi ve söz dizimi kuralları içeren belirlenebilir bir dil bilgisine sahip olması gerekir. Buna bağlamdan bağımsız dil bilgisi denir. İnsan dilleri bu tür diller olmadığından geleneksel ayrıştırma teknikleriyle ayrıştırılamaz.

Ayrıştırıcı - Söz dizimi analizörü kombinasyonu

Ayrıştırma işlemi, iki alt sürece ayrılabilir: söz dizimi analizi ve söz dizimi analizi.

Sözcük analizi, girişin jetonlara bölünmesi işlemidir. Jetonlar, geçerli yapı taşlarının bir araya getirilmesiyle oluşan dil kelime hazinesidir. Bu dil için sözlükte yer alan tüm kelimelerden oluşur.

Söz dizimi analizi, dil söz dizimi kurallarının uygulanmasıdır.

Ayrıştırıcılar genellikle işi iki bileşen arasında böler: Giriş karakter dizisini geçerli jetonlara bölme sorumlusu olan token ayırıcı (bazen jeton dizilici olarak da adlandırılır) ve belge yapısını dil söz dizimi kurallarına göre analiz ederek ayrıştırma ağacını oluşturmaktan sorumlu olan ayrıştırıcı.

Ayrıştırıcı, boşluklar ve satır sonları gibi alakasız karakterleri nasıl kaldıracağını bilir.

Kaynak dokümandan ayrıştırma ağaçlarına
Şekil 6: Kaynak belgeden ayrıştırma ağaçlarına

Ayrıştırma işlemi yinelemeli bir süreçtir. Ayrıştırıcı genellikle dize ayrıştırıcıdan yeni bir jeton ister ve jetonu söz dizimi kurallarından biriyle eşleştirmeye çalışır. Bir kural eşleşirse jetona karşılık gelen bir düğüm ayrıştırma ağacına eklenir ve ayrıştırıcı başka bir jeton ister.

Hiçbir kural eşleşmezse ayrıştırıcı, jetonu dahili olarak depolar ve dahili olarak depolanan tüm jetonlarla eşleşen bir kural bulunana kadar jeton istemeye devam eder. Hiçbir kural bulunmazsa ayrıştırıcı bir istisna oluşturur. Bu durum, belgenin geçerli olmadığı ve söz dizimi hataları içerdiği anlamına gelir.

Çeviri

Çoğu durumda, ayrıştırma ağacı nihai ürün değildir. Ayrıştırma, çeviride sıklıkla kullanılır: Giriş dokümanı başka bir biçime dönüştürülür. Derleme buna örnek gösterilebilir. Kaynak kodunu makine koduna derleyen derleyici, önce kodu bir ayrıştırma ağacına ayırır, ardından ağacı bir makine kodu belgesine çevirir.

Derleme akışı
Şekil 7: Derleme akışı

Ayrıştırma örneği

5. resimde, matematiksel bir ifadeden bir ayrıştırma ağacı oluşturduk. Basit bir matematik dili tanımlayıp ayrıştırma işlemini inceleyelim.

Söz dizimi:

  1. Dil söz dizimi yapı taşları ifadeler, terimler ve işlemlerdir.
  2. Dilimiz herhangi bir sayıda ifade içerebilir.
  3. Bir ifade, "terim", ardından "işlem" ve ardından başka bir terim olarak tanımlanır.
  4. İşlem, artı veya eksi jetondur.
  5. Terim, tam sayı jetonu veya ifadedir

2 + 3 - 1 girişini analiz edelim.

Bir kuralla eşleşen ilk alt dize 2'tür: 5. kurala göre bu bir terimdir. İkinci eşleme 2 + 3: Bu, üçüncü kuralla eşleşir: Bir terimin ardından bir işlem ve ardından başka bir terim. Sonraki eşleme yalnızca girişin sonunda gerçekleşir. 2 + 3 bir terim olduğu için 2 + 3 - 1 bir ifadedir. Dolayısıyla, bir terimin ardından bir işlem ve ardından başka bir terimimiz var. 2 + + hiçbir kuralla eşleşmez ve bu nedenle geçersiz bir giriştir.

Sözlük ve söz dizimi için resmi tanımlar

Kelime dağarcığı genellikle normal ifadelerle ifade edilir.

Örneğin, dilimiz şu şekilde tanımlanır:

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

Gördüğünüz gibi, tam sayılar normal ifadeyle tanımlanır.

Söz dizimi genellikle BNF adlı bir biçimde tanımlanır. Dilimiz şu şekilde tanımlanır:

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

Bir dilin söz dizimi bağlamsız söz dizimiyse normal ayrıştırıcılar tarafından ayrıştırılabileceğini söylemiştik. Bağlamsız söz dizimi, tamamen BNF ile ifade edilebilen bir söz dizimidir. Resmi bir tanım için Wikipedia'nın Bağlamdan Bağımsız Dil Bilgisi makalesine bakın.

Ayrıştırıcı türleri

İki tür ayrıştırıcı vardır: yukarıdan aşağı ayrıştırıcılar ve aşağıdan yukarı ayrıştırıcılar. Üstten aşağı ayrıştırıcıların, söz dizisinin üst düzey yapısını inceleyip kural eşleşmesi bulmaya çalıştıklarını sezgisel bir şekilde açıklayabiliriz. Aşağıdan yukarıya doğru ayrıştırıcılar, girişle başlar ve girişi düşük düzeyli kurallardan başlayarak yüksek düzeyli kurallar karşılanana kadar yavaş yavaş söz dizimi kurallarına dönüştürür.

İki tür ayrıştırıcının örneğimizi nasıl ayrıştırdığını görelim.

Yukarıdan aşağı ayrıştırıcı, daha yüksek düzeydeki kuraldan başlar: 2 + 3 ifadesi olarak tanımlar. Ardından 2 + 3 - 1 ifadesi tanımlanır (ifadeyi tanımlama süreci, diğer kurallarla eşleşerek gelişir ancak başlangıç noktası en üst düzey kuraldır).

Aşağıdan yukarıya doğru ayrıştırıcı, bir kural eşleşene kadar girişi tarar. Ardından, eşleşen girişi kuralla değiştirir. Bu işlem, girişin sonuna kadar devam eder. Kısmen eşleşen ifade, ayrıştırıcının yığınına yerleştirilir.

Yığınla Giriş
2 + 3 - 1
terim + 3 - 1
terim işlemi 3 - 1
ifade - 1
ifade işlemi 1
ifade -

Giriş sağa kaydırılıp (ilk olarak girişin başlangıcını işaret eden ve sağa doğru hareket eden bir işaretçi düşünün) yavaş yavaş söz dizimi kurallarına indirgendiği için bu tür bir aşağıdan yukarıya ayrıştırıcıya kaydırma-azaltma ayrıştırıcısı denir.

Ayrıştırıcıları otomatik olarak oluşturma

Ayrıştırıcı oluşturabilen araçlar vardır. Dilinizin dil bilgisini (sözlüğü ve söz dizimi kuralları) beslediğinizde çalışan bir ayrıştırıcı oluştururlar. Ayrıştırıcı oluşturmak için ayrıştırma hakkında derin bir bilgi sahibi olmanız gerekir. Ayrıca, optimize edilmiş bir ayrıştırıcıyı manuel olarak oluşturmak kolay değildir. Bu nedenle, ayrıştırıcı oluşturucular çok yararlı olabilir.

WebKit, iki iyi bilinen ayrıştırıcı oluşturucu kullanır: Ayrıştırıcı oluşturmak için Flex ve ayrıştırıcı oluşturmak için Bison (bunlara Lex ve Yacc adlarıyla da rastlayabilirsiniz). Flex girişi, jetonların normal ifade tanımlarını içeren bir dosyadır. Bison'un girişi, BNF biçimindeki dil söz dizimi kurallarıdır.

HTML Ayrıştırıcı

HTML ayrıştırıcının görevi, HTML işaretlemesini bir ayrıştırma ağacına ayrıştırmaktır.

HTML dil bilgisi

HTML'nin kelime dağarcığı ve söz dizimi, W3C kuruluşu tarafından oluşturulan spesifikasyonlarda tanımlanır.

Ayrıştırma girişinde gördüğümüz gibi, dil bilgisi söz dizimi BNF gibi biçimler kullanılarak resmi olarak tanımlanabilir.

Maalesef geleneksel ayrıştırıcı konuların tümü HTML için geçerli değildir (Bunları sadece eğlence için gündeme getirmedim; CSS ve JavaScript'i ayrıştırırken kullanılacaklardır). HTML, ayrıştırıcıların ihtiyaç duyduğu bağlamsız bir dil bilgisiyle kolayca tanımlanamaz.

HTML'yi tanımlamak için resmi bir biçim (DTD - Document Type Definition) vardır ancak bu bağlamdan bağımsız bir dil bilgisi değildir.

Bu durum ilk bakışta garip görünebilir; HTML, XML'e oldukça yakındır. Birçok XML ayrıştırıcı mevcuttur. HTML'nin XML varyantı olan XHTML'in ne gibi avantajları vardır?

Aradaki fark, HTML yaklaşımının daha "affedici" olmasıdır: Belirli etiketleri (daha sonra dolaylı olarak eklenir) veya bazen başlangıç ya da bitiş etiketlerini atlamanıza olanak tanır. Genel olarak, XML'in katı ve talepkar söz dizimine kıyasla "yumuşak" bir söz dizimidir.

Küçük görünen bu ayrıntı, büyük bir fark yaratabilir. HTML'nin bu kadar popüler olmasının başlıca nedeni, hatalarınızı bağışlaması ve web yazarı için hayatı kolaylaştırmasıdır. Diğer yandan, biçimsel bir dil bilgisi yazmanızı zorlaştırır. Özetlemek gerekirse, dil bilgisi bağlamdan bağımsız olmadığı için HTML, geleneksel ayrıştırıcılar tarafından kolayca ayrıştırılamaz. HTML, XML ayrıştırıcılar tarafından ayrıştırılamaz.

HTML DTD

HTML tanımı DTD biçimindedir. Bu biçim, SGML ailesinin dillerini tanımlamak için kullanılır. Biçim, izin verilen tüm öğelerin, özelliklerinin ve hiyerarşisinin tanımlarını içerir. Daha önce de gördüğümüz gibi, HTML DTD bağlamdan bağımsız bir dil bilgisi oluşturmaz.

DTD'nin birkaç varyasyonu vardır. Katı mod yalnızca spesifikasyonlara uygundur ancak diğer modlar, tarayıcılar tarafından geçmişte kullanılan işaretleme için destek içerir. Amaç, eski içeriklerle geriye dönük uyumluluk sağlamaktır. Mevcut katı DTD şu adrestedir: www.w3.org/TR/html4/strict.dtd

DOM

Çıkış ağacı ("ayrıştırma ağacı"), DOM öğesi ve özellik düğümlerinden oluşan bir ağaçtır. DOM, Belge Nesne Modeli'nin kısaltmasıdır. HTML dokümanındaki nesne sunumu ve HTML öğelerinin JavaScript gibi dış dünyayla arayüzüdür.

Ağacın kökü "Document" nesnesidir.

DOM, işaretlemeyle neredeyse bire bir ilişkilidir. Örneğin:

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

Bu işaretleme aşağıdaki DOM ağacına çevrilir:

Örnek işaretlemenin DOM ağacı
Şekil 8: Örnek işaretlemenin DOM ağacı

HTML gibi DOM da W3C kuruluşu tarafından belirtilir. www.w3.org/DOM/DOMTR adresine bakın. Belgeleri değiştirmeye yönelik genel bir spesifikasyondur. Belirli bir modül, HTML'ye özgü öğeleri açıklar. HTML tanımlarını www.w3.org/TR/2003/REC-DOM-Level-2-HTML-20030109/idl-definitions.html adresinde bulabilirsiniz.

Ağacın DOM düğümleri içerdiğini söylerken, ağacın DOM arayüzlerinden birini uygulayan öğelerden oluştuğunu kastediyorum. Tarayıcılar, tarayıcı tarafından dahili olarak kullanılan başka özelliklere sahip somut uygulamalar kullanır.

Ayrıştırma algoritması

Önceki bölümlerde gördüğümüz gibi, HTML normal yukarıdan aşağıya veya aşağıdan yukarıya ayrıştırıcılar kullanılarak ayrıştırılamaz.

Bunun nedenleri şunlardır:

  1. Dilin bağışlayıcı yapısı.
  2. Tarayıcıların, bilinen geçersiz HTML durumlarını desteklemek için geleneksel hata toleransına sahip olması.
  3. Ayrıştırma işlemi yeniden girilebilir. Diğer diller için kaynak, ayrıştırma sırasında değişmez ancak HTML'de dinamik kod (document.write() çağrıları içeren komut dosyası öğeleri gibi) ek jetonlar ekleyebilir. Bu nedenle, ayrıştırma işlemi aslında girişi değiştirir.

Normal ayrıştırma tekniklerini kullanamayan tarayıcılar, HTML'yi ayrıştırmak için özel ayrıştırıcılar oluşturur.

Ayrıştırma algoritması, HTML5 spesifikasyonunda ayrıntılı olarak açıklanır. Algoritma iki aşamadan oluşur: jeton oluşturma ve ağaç oluşturma.

Örneklendirme, girişi örnekçelere ayıran söz dizimi analizidir. HTML jetonları arasında başlangıç etiketleri, bitiş etiketleri, özellik adları ve özellik değerleri bulunur.

Ayrıştırıcı, jetonu tanır, ağaç oluşturucuya verir ve sonraki jetonu tanımak için sonraki karakteri tüketir. Bu işlem, girişin sonuna kadar devam eder.

HTML ayrıştırma akışı (HTML5 spesifikasyonundan alınmıştır)
Şekil 9: HTML ayrıştırma akışı (HTML5 spesifikasyonundan alınmıştır)

Jeton oluşturma algoritması

Algoritmanın çıktısı bir HTML jetonudur. Algoritma, durum makinesi olarak ifade edilir. Her durum, giriş akışındaki bir veya daha fazla karakteri tüketir ve bir sonraki durumu bu karakterlere göre günceller. Karar, mevcut jeton oluşturma durumu ve ağaç oluşturma durumundan etkilenir. Bu, aynı tüketilen karakterin, mevcut duruma bağlı olarak doğru bir sonraki durum için farklı sonuçlar vereceği anlamına gelir. Algoritma, tam olarak açıklanamayacak kadar karmaşıktır. Bu nedenle, ilkeyi anlamamıza yardımcı olacak basit bir örnekle başlayalım.

Temel örnek: Aşağıdaki HTML'nin jetonlara ayrılması:

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

İlk durum "Veri durumu"dur. < karakteriyle karşılaşıldığında durum "Etiket açık durumu" olarak değiştirilir. Bir a-z karakteri tüketildiğinde "Başlangıç etiketi jetonu" oluşturulur ve durum "Etiket adı durumu" olarak değiştirilir. > karakteri tüketilene kadar bu durumda kalırız. Her karakter yeni jeton adına eklenir. Örneğimizde oluşturulan jeton bir html jetonudur.

> etiketine ulaşıldığında mevcut jeton yayınlanır ve durum "Veri durumu" olarak geri döner. <body> etiketi de aynı adımlarla işlenir. Şu ana kadar html ve body etiketleri yayınlandı. Şimdi "Veri durumu" sayfasına geri döndük. Hello world'un H karakteri tüketildiğinde bir karakter jetonu oluşturulur ve yayınlanır. Bu işlem, </body>'ın < karakterine ulaşılana kadar devam eder. Hello world karakterinin her biri için bir karakter jetonu göndeririz.

Şimdi "Etiket açık durumu"'na geri döndük. Sonraki giriş / tüketildiğinde bir end tag token oluşturulur ve "Etiket adı durumu"'na geçilir. > değerine ulaşana kadar yine bu durumda kalırız.Ardından yeni etiket jetonu yayınlanır ve "Veri durumu"'na geri döneriz. </html> girişi, önceki örnekte olduğu gibi değerlendirilir.

Örnek girişi dizelere ayırma
Şekil 10: Örnek girişin jetonlara ayrılması

Ağaç oluşturma algoritması

Ayrıştırıcı oluşturulduğunda Document nesnesi de oluşturulur. Ağaç oluşturma aşamasında, kökünde Document bulunan DOM ağacı değiştirilir ve ağaca öğeler eklenir. Ayrıştırıcı tarafından oluşturulan her düğüm, ağaç oluşturucu tarafından işlenir. Spesifikasyon, her jeton için hangi DOM öğesinin onunla alakalı olduğunu ve bu jeton için oluşturulacağını tanımlar. Öğe, DOM ağacına ve açık öğe yığınına eklenir. Bu yığın, iç içe yerleştirme uyuşmazlıklarını ve kapatılmamış etiketleri düzeltmek için kullanılır. Algoritma, durum makinesi olarak da açıklanır. Bu durumlara "ekleme modları" denir.

Örnek giriş için ağaç oluşturma sürecine göz atalım:

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

Ağaç oluşturma aşamasına girilen giriş, jeton oluşturma aşamasından alınan bir jeton dizisidir. İlk mod "ilk mod"'dur. "html" jetonunun alınması, "html'den önce" moduna geçilmesine ve jetonun bu modda yeniden işlenmesine neden olur. Bu işlem, kök Document nesnesine eklenecek HTMLHtmlElement öğesinin oluşturulmasına neden olur.

Durum "before head" olarak değiştirilir. Ardından "body" jetonu alınır. "head" jetonumuz olmasa da bir HTMLHeadElement örtük olarak oluşturulur ve ağaca eklenir.

Şimdi "kafa içinde" moduna, ardından "kafadan sonra" moduna geçiyoruz. Gövde jetonu yeniden işlenir, bir HTMLBodyElement oluşturulup eklenir ve mod "in body" olarak aktarılır.

"Merhaba dünya" dizesinin karakter jetonları artık alınır. İlki bir "Metin" düğümünün oluşturulmasına ve eklenmesine neden olur ve diğer karakterler bu düğüme eklenir.

Gövde sonu jetonunun alınması, "gövden sonra" moduna geçişe neden olur. Ardından, bizi "body'dan sonra" moduna geçirecek html bitiş etiketini alırız. Dosya sonu jetonu alındığında ayrıştırma işlemi sonlandırılır.

Örnek HTML&#39;nin ağaç yapısı.
Şekil 11: Örnek HTML'nin ağaç yapısı

Ayrıştırma işlemi tamamlandığında yapılacak işlemler

Bu aşamada tarayıcı, dokümanı etkileşimli olarak işaretler ve "ertelenen" modda olan komut dosyalarını ayrıştırmaya başlar: doküman ayrıştırıldıktan sonra yürütülmesi gerekenler. Ardından belge durumu "tamamlandı" olarak ayarlanır ve bir "yükleme" etkinliği tetiklenir.

HTML5 spesifikasyonunda jeton oluşturma ve ağaç oluşturma algoritmalarının tamamını görebilirsiniz.

Tarayıcıların hata toleransı

HTML sayfalarında hiçbir zaman "Geçersiz Söz Dizimi" hatası almazsınız. Tarayıcılar geçersiz içerikleri düzeltip devam eder.

Aşağıdaki HTML'yi örnek olarak alalım:

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

Yaklaşık bir milyon kuralı ihlal etmiş olmalıyım ("mytag" standart bir etiket değil, "p" ve "div" öğelerinin yanlış yerleştirilmesi ve daha fazlası) ancak tarayıcı yine de doğru şekilde gösteriyor ve şikayet etmiyor. Bu nedenle, ayrıştırıcı kodunun büyük bir kısmı HTML yazarı hatalarını düzeltiyor.

Hata işleme, tarayıcılarda oldukça tutarlı bir şekilde uygulanır ancak şaşırtıcı bir şekilde HTML spesifikasyonlarının bir parçası değildir. Yer işareti ekleme ve geri/ileri düğmeleri gibi, bu da tarayıcılarda yıllar içinde geliştirilmiş bir özelliktir. Birçok sitede tekrarlanan geçersiz HTML yapıları vardır ve tarayıcılar bunları diğer tarayıcılarla uyumlu olacak şekilde düzeltmeye çalışır.

HTML5 spesifikasyonunda bu koşulların bazıları tanımlanmıştır. (WebKit, bunu HTML ayrıştırıcı sınıfının başındaki yorumda güzel bir şekilde özetler.)

Ayrıştırıcı, dokümana ayrıştırılmış girişi ayrıştırarak doküman ağacını oluşturur. Doküman iyi biçimlendirilmişse ayrıştırması kolaydır.

Maalesef iyi biçimlendirilmemiş çok sayıda HTML belgesiyle ilgilenmemiz gerekiyor. Bu nedenle, ayrıştırıcının hatalar konusunda hoşgörülü olması gerekiyor.

En azından aşağıdaki hata koşullarını ele almamız gerekir:

  1. Eklenen öğe, bazı dış etiketlerin içinde açıkça yasaklanmıştır. Bu durumda, öğeyi yasaklayana kadar tüm etiketleri kapatıp öğeyi daha sonra eklememiz gerekir.
  2. Öğeyi doğrudan eklememize izin verilmiyor. Dokümanı yazan kişi, aradaki bazı etiketleri unutmuş olabilir (veya aradaki etiket isteğe bağlı olabilir). Bu durum aşağıdaki etiketler için geçerli olabilir: HTML HEAD BODY TBODY TR TD LI (unuttuğum var mı?).
  3. Satır içi bir öğenin içine bir blok öğesi eklemek istiyoruz. Bir sonraki üst blok öğeye kadar tüm satır içi öğeleri kapatın.
  4. Bu işe yaramazsa öğeyi eklememize izin verilene kadar öğeleri kapatın veya etiketi yoksayın.

WebKit hata toleransı örneklerini inceleyelim:

<br> yerine </br>

Bazı siteler <br> yerine </br> kullanır. WebKit, IE ve Firefox ile uyumlu olmak için bunu <br> olarak ele alır.

Kod:

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

Hata işleme işleminin dahili olduğunu ve kullanıcıya gösterilmeyeceğini unutmayın.

Dağınık bir tablo

Başıboş tablo, başka bir tablonun içinde bulunan ancak tablo hücresinin içinde bulunmayan bir tablodur.

Örneğin:

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

WebKit, hiyerarşiyi iki kardeş tabloyla değiştirir:

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

Kod:

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

WebKit, mevcut öğe içerikleri için bir yığın kullanır: İç tabloyu dış tablo yığınından çıkarır. Tablolar artık kardeş olur.

İç içe yerleştirilmiş form öğeleri

Kullanıcı bir formu başka bir formun içine yerleştirirse ikinci form yok sayılır.

Kod:

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

Çok derin bir etiket hiyerarşisi

Yorum her şeyi açıklıyor.

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

Yanlış yerleştirilmiş html veya body son etiketleri

Yorumlar her şeyi açıklıyor.

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

Bu nedenle, web yazarları WebKit hata toleransı kod snippet'inde örnek olarak görünmek istemiyorlarsa iyi biçimlendirilmiş HTML yazmalıdır.

CSS ayrıştırma

Girişteki ayrıştırma kavramlarını hatırlıyor musunuz? HTML'den farklı olarak CSS, bağlamsız bir dil bilgisidir ve girişte açıklanan ayrıştırıcı türleri kullanılarak ayrıştırılabilir. Aslında CSS spesifikasyonu, CSS sözcüksel ve söz dizimi dil bilgisini tanımlar.

Birkaç örneğe göz atalım:

Sözlük dil bilgisi (sözlük), her bir parça için normal ifadelerle tanımlanır:

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", sınıf adı gibi tanımlayıcıların kısaltmasıdır. "ad", bir öğe kimliğidir ("#" ile belirtilir).

Söz dizimi dil bilgisi BNF'de açıklanır.

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*] ')' ]
  ;

Açıklama:

Kural kümesi şu yapıdadır:

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

div.error ve a.error seçicilerdir. Açılı parantez içindeki kısım, bu kural kümesi tarafından uygulanan kuralları içerir. Bu yapı, aşağıdaki tanımda resmi olarak tanımlanmıştır:

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

Bu, kural kümesinin bir seçici veya isteğe bağlı olarak virgül ve boşluklarla ayrılmış bir dizi seçici (Boşluk karakteri S ile gösterilir) olduğu anlamına gelir. Kural kümesi, köşeli parantez içerir ve bunların içinde bir bildirim veya isteğe bağlı olarak noktalı virgülle ayrılmış birkaç bildirim bulunur. "declaration" ve "selector", aşağıdaki BNF tanımlarında açıklanacaktır.

WebKit CSS ayrıştırıcısı

WebKit, CSS dil bilgisi dosyalarından otomatik olarak ayrıştırıcılar oluşturmak için Flex ve Bison ayrıştırıcı oluşturucularını kullanır. Ayıklama aracı girişinde de belirtildiği gibi, Bison alttan yukarı doğru kaydırma azaltma ayrıştırıcısı oluşturur. Firefox, manuel olarak yazılmış bir yukarıdan aşağıya ayrıştırıcı kullanır. Her iki durumda da her CSS dosyası bir StyleSheet nesnesine ayrıştırılır. Her nesne CSS kuralları içerir. CSS kural nesneleri, CSS diline karşılık gelen seçici ve bildirim nesneleri ile diğer nesneleri içerir.

CSS&#39;yi ayrıştırma.
Şekil 12: CSS'yi ayrıştırma