Jak działają przeglądarki

Za kulisami nowoczesnych przeglądarek

Wstęp

Ten kompleksowy przewodnik na temat wewnętrznych operacji WebKit i Gecko jest wynikiem wielu badań przeprowadzonych przez izraelskiego programistę Tali Garsiela. W ciągu kilku lat przejrzała wszystkie opublikowane dane o jej wewnętrznych funkcjach i spędziła dużo czasu na czytaniu jej kodu źródłowego. Napisała:

Jako programista stron internetowych zapoznanie się z działalnością przeglądarki pomoże Ci podejmować lepsze decyzje i poznać uzasadnienia stojące za sprawdzonymi metodami programowania. Ten dokument jest dość długi, ale warto go przeanalizować. Warto to zrobić.

Paul Ireland, zespół ds. relacji z deweloperami Chrome

Wstęp

Przeglądarki to najpopularniejsze oprogramowanie. W tym kursie wyjaśnię, jak działają zza kulis. Będziemy sprawdzać, co się stanie, gdy wpiszesz google.com w pasku adresu, aż na ekranie przeglądarki pojawi się strona Google.

Przeglądarki, które omówimy

Na komputerach jest obecnie pięć głównych przeglądarek: Chrome, Internet Explorer, Firefox, Safari i Opera. Na urządzeniach mobilnych główne przeglądarki to: Android, Android, iPhone, Opera Mini i Opera Mobile, UC Browser, Nokia S40/S60 oraz Chrome, wszystkie z nich (z wyjątkiem przeglądarek Opera) bazują na technologii WebKit. Podam przykłady z przeglądarek open source Firefox i Chrome oraz Safari (otwarte częściowo). Według statystyk StatCounter (dane z czerwca 2013 r.) Chrome, Firefox i Safari odpowiadają za około 71% używania przeglądarek na komputerach na całym świecie. W przypadku urządzeń mobilnych, przeglądarki na Androida, iPhone'a i Chrome stanowią około 54% wykorzystania.

Główne funkcje przeglądarki

Główną funkcją przeglądarki jest prezentowanie wybranego zasobu internetowego przez wysłanie żądania do serwera i wyświetlenie go w oknie przeglądarki. Jest to zazwyczaj dokument HTML, ale może to być też plik PDF, obraz lub inny rodzaj treści. Lokalizacja zasobu jest określana przez użytkownika przy użyciu identyfikatora URI (Uniform Resource Identifier).

Sposób, w jaki przeglądarka interpretuje i wyświetla pliki HTML, jest określony w specyfikacji HTML i CSS. Specyfikacje te zostały opracowane przez organizację W3C (World Wide Web Consortium), czyli organizację zajmującą się standardami w zakresie standardów internetowych. Przez lata spełniały one tylko część specyfikacji i opracowywały własne rozszerzenia. Wywołało to poważne problemy ze zgodnością twórców stron internetowych. Obecnie większość przeglądarek w większym lub mniejszym stopniu odpowiada specyfikacjom.

Interfejsy użytkownika przeglądarki mają wiele wspólnego ze sobą. Najczęściej używane elementy interfejsu to:

  1. Pasek adresu umożliwiający wstawienie identyfikatora URI
  2. Przyciski wstecz i do przodu
  3. Opcje zakładek
  4. Przyciski odświeżania i zatrzymywania służą do odświeżania lub zatrzymywania ładowania bieżących dokumentów
  5. Przycisk strony startowej

Co ciekawe, interfejs użytkownika nie został określony w żadnej oficjalnej specyfikacji, lecz w wyniku naśladowania przez wiele lat sprawdzonych metod i przeglądarek. Specyfikacja HTML5 nie definiuje elementów interfejsu, które musi mieć przeglądarka, ale wymienia kilka typowych elementów. Są to między innymi pasek adresu, pasek stanu i pasek narzędzi. Istnieją oczywiście funkcje, które są dostępne tylko w przypadku danej przeglądarki, na przykład menedżer pobierania w przeglądarce Firefox.

Infrastruktura wysokiego poziomu

Główne komponenty przeglądarki to:

  1. Interfejs użytkownika: obejmuje pasek adresu, przycisk Wstecz/Dalej, menu dodawania zakładek itp. Wyświetlane są wszystkie części przeglądarki z wyjątkiem okna, w którym wyświetla się żądana strona.
  2. Mechanizm przeglądarki: pośredniczy w działaniach między interfejsem użytkownika a silnikiem renderowania.
  3. Mechanizm renderowania: odpowiedzialny za wyświetlanie żądanej treści. Jeśli np. żądana treść to HTML, mechanizm renderowania analizuje kod HTML i CSS, a potem wyświetla przeanalizowaną treść na ekranie.
  4. Sieć: w przypadku wywołań sieciowych, takich jak żądania HTTP, z zastosowaniem różnych implementacji na różnych platformach za pomocą interfejsu niezależnego od platformy.
  5. Backend interfejsu użytkownika: używany do rysowania podstawowych widżetów, takich jak pola złożone i okna. Ten backend udostępnia ogólny interfejs niezwiązany z konkretną platformą. Zawiera metody interfejsu systemu operacyjnego.
  6. Interpreter JavaScriptu. Służy do analizowania i wykonywania kodu JavaScript.
  7. Przechowywanie danych. Jest to warstwa trwałości. Możliwe, że przeglądarka będzie musiała zapisywać lokalnie wszelkiego rodzaju dane, np. pliki cookie. Przeglądarki obsługują również mechanizmy pamięci masowej, takie jak localStorage, IndexedDB, WebSQL i FileSystem.
Komponenty przeglądarki
Rys. 1. Komponenty przeglądarki

Pamiętaj, że przeglądarki, takie jak Chrome, uruchamiają wiele instancji silnika renderowania – po jednym na każdą kartę. Każda karta jest uruchamiana w oddzielnym procesie.

Mechanizmy renderowania

Mechanizm renderowania jest odpowiedzialny za... Renderowanie, czyli wyświetlanie żądanej treści na ekranie przeglądarki.

Domyślnie silnik renderowania może wyświetlać dokumenty HTML i XML oraz obrazy. Może wyświetlać inne typy danych przy użyciu wtyczek lub rozszerzeń, np. wyświetlać dokumenty PDF przy użyciu wtyczki do przeglądarki PDF. Jednak w tym rozdziale skupimy się na głównym przypadku użycia: wyświetlaniu kodu HTML i obrazów sformatowanych przy użyciu CSS.

Różne przeglądarki korzystają z różnych silników renderowania: Internet Explorer korzysta z Trident, Firefox z gekonem, Safari – z WebKit. Chrome i Opera (od wersji 15) używają Blink – rozwidlenia WebKit.

WebKit to mechanizm renderujący typu open source, który powstał jako silnik dla platformy Linux, a został zmodyfikowany przez firmę Apple tak, aby obsługiwał komputery Mac i Windows.

Główne założenia

Mechanizm renderujący zacznie pobierać zawartość żądanego dokumentu z warstwy sieciowej. Zwykle odbywa się to w fragmentach o rozmiarze 8 KB.

Tak wygląda podstawowy przepływ mechanizmu renderowania:

Podstawowy przepływ mechanizmu renderowania
Rysunek 2. Podstawowy przepływ mechanizmu renderowania

Mechanizm renderowania rozpocznie analizowanie dokumentu HTML i przekształca elementy w węzły DOM w drzewie o nazwie „drzewo treści”. Wyszukiwarka przeanalizuje dane stylu zarówno w zewnętrznych plikach CSS, jak i w elementach stylu. Informacje o stylu oraz instrukcje wizualne w kodzie HTML zostaną użyte do utworzenia kolejnego drzewa: drzewa renderowania.

Drzewo renderowania zawiera prostokąty z atrybutami wizualnymi, takimi jak kolor i wymiary. Prostokąty wyświetlają się na ekranie w odpowiedniej kolejności.

Po utworzeniu drzewa renderowania przechodzi proces „układu”. Oznacza to podanie każdemu węzłowi dokładnej lokalizacji, w której powinien on pojawić się na ekranie. Kolejny etap to malowanie – zostanie przemierzone drzewo renderowania, a każdy węzeł zostanie namalowany z wykorzystaniem warstwy backendu interfejsu.

Trzeba pamiętać, że ten proces odbywa się stopniowo. Dla wygody użytkowników mechanizm renderujący spróbuje jak najszybciej wyświetlić zawartość ekranu. Nie zaczeka, aż cały kod HTML zostanie przeanalizowany, zanim zacznie tworzyć i układać drzewo renderowania. Fragmenty treści zostaną przeanalizowane i wyświetlone, a proces będzie kontynuowany z resztą treści pozyskiwanych z sieci.

Przykłady głównego przepływu

Główny proces WebKit.
Ilustracja 3. Główny proces WebKit
Główny przepływ mechanizmu renderowania Gecko Mozilla.
Rysunek 4. Główny przepływ mechanizmu renderowania Mozilla's Gecko

Na rysunkach 3 i 4 widać, że chociaż WebKit i Gecko używają nieco innej terminologii, proces jest zasadniczo taki sam.

Gekon nazywa drzewo sformatowanych elementów drzewem ramek. Każdy element jest ramką. WebKit używa terminu „drzewo renderowania” i składa się z „obiektów renderowania”. Gekon używa terminu „układ” do określania rozmieszczenia elementów, natomiast Gecko określa to jako „reflow”. „Przyłącze” to termin WebKit łączący węzły DOM i informacje wizualne w celu utworzenia drzewa renderowania. Nieznaczna różnica polega na tym, że gekon ma dodatkową warstwę między kodem HTML a drzewem DOM. Nazywa się go „ujściem treści” i fabryką tworzenia elementów DOM. Omówimy każdy etap procesu:

Analiza – ogólne

Analiza jest bardzo ważną czynnością w mechanizmie renderowania, więc omówimy ją trochę dokładniej. Zacznijmy od krótkiego wprowadzenia o analizowaniu danych.

Analiza dokumentu oznacza przekształcenie go w strukturę, z której może korzystać kod. W wyniku analizy powstaje zwykle drzewo węzłów, które reprezentują strukturę dokumentu. Nazywa się to drzewem analizy lub drzewem składni.

Na przykład analiza wyrażenia 2 + 3 - 1 może zwrócić takie drzewo:

Węzeł drzewa wyrażeń matematycznych.
Rys. 5: węzeł drzewa wyrażeń matematycznych

Gramatyka

Analiza zależy od reguł składni przestrzeganych w dokumencie: języka lub formatu, w którym został napisany. Każdy format, który można przeanalizować, musi mieć deterministyczną gramatykę obejmującą reguły słownictwa i składni. Jest to tzw. gramatyka wolna od kontekstu. Języki ludzkie nie są tymi językami, więc nie można ich analizować za pomocą konwencjonalnych technik analizy.

Parser – kombinacja Lexera

Analizę można podzielić na 2 podprocesy: analizę leksyczną i analizę składni.

Analiza leksyka to proces dzielenia danych wejściowych na tokeny. Tokeny to słownictwo językowe, czyli zbiór prawidłowych elementów składowych. W języku ludzkim będzie składać się ze wszystkich słów, które pojawiają się w słowniku danego języka.

Analiza składni polega na stosowaniu reguł składni języka.

Parsery zazwyczaj dzielą pracę na 2 komponenty: leksera (czasem nazywanego tokenizatorem), który odpowiada za podział danych wejściowych na prawidłowe tokeny, oraz parsera, który odpowiada za utworzenie drzewa analizy składniowej na podstawie analizy struktury dokumentu zgodnie z regułami składni języka.

Lekser wie, jak usuwać nieistotne znaki, takie jak spacje czy podziały wierszy.

Z dokumentu źródłowego do analizy drzew
Rysunek 6. Od dokumentu źródłowego do analizy drzew

Proces analizy jest iteracyjny. Parser zazwyczaj prosi leksera o nowy token i próbuje go dopasować do jednej z reguł składni. Jeśli reguła zostanie dopasowana, do drzewa analizy zostanie dodany węzeł odpowiadający tokenowi, a parser poprosi o kolejny token.

Jeśli żadna reguła nie pasuje, parser zapisze token wewnętrznie i będzie pytać o tokeny, dopóki nie zostanie znaleziona reguła pasująca do wszystkich tokenów przechowywanych wewnętrznie. Jeśli nie zostanie znaleziona żadna reguła, parser zgłosi wyjątek. Oznacza to, że dokument był nieprawidłowy i zawiera błędy składniowe.

Tłumaczenie

W wielu przypadkach drzewo analizy nie jest końcowym produktem. Analiza jest często stosowana w tłumaczeniu, czyli przekształcaniu dokumentu wejściowego na inny format. Przykładem może być kompilacja. Kompilator, który kompiluje kod źródłowy w kod maszynowy, najpierw analizuje go w drzewie analizy, a potem przekształca je w dokument z kodem maszynowym.

Proces kompilacji
Rys. 7. Przebieg kompilacji

Przykład analizy

Na Rysunku 5 utworzyliśmy drzewo analizy na podstawie wyrażenia matematycznego. Spróbujmy zdefiniować prosty język matematyczny i zobaczmy proces analizowania.

Składnia:

  1. Elementy składowe składni języka to wyrażenia, terminy i operacje.
  2. Nasz język może zawierać dowolną liczbę wyrażeń.
  3. Wyrażenie jest zdefiniowane jako „hasło”, po którym następuje „operacja”, po której następuje inne hasło
  4. Operacja jest tokenem plus lub minus
  5. Hasło to token lub wyrażenie składające się z liczby całkowitej

Przeanalizujmy dane wejściowe 2 + 3 - 1.

Pierwszy podłańcuch pasujący do reguły to 2: zgodnie z regułą nr 5 jest to hasło. Drugie dopasowanie to 2 + 3: pasuje do trzeciej reguły: hasła, po którym następuje operacja, a następnie inne hasło. Następne dopasowanie zostanie odnotowane dopiero na końcu danych wejściowych. 2 + 3 - 1 to wyrażenie, ponieważ wiemy, że 2 + 3 jest terminem, więc mamy do czynienia z hasłem, po którym następuje operacja, po której następuje inne hasło. 2 + + nie będzie pasować do żadnej reguły i dlatego jest nieprawidłowym danymi wejściowymi.

Formalne definicje słownictwa i składni

Słownictwo jest zwykle wyrażane za pomocą wyrażeń regularnych.

Naszym językiem będzie na przykład:

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

Jak widać, liczby całkowite są definiowane za pomocą wyrażenia regularnego.

Składnia jest zwykle definiowana w formacie BNF. Definiujemy ten język jako:

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

Powiedzieliśmy, że gramatyka języka może być analizowana przez zwykłe parsery, jeśli jego gramatyka nie wymaga kontekstu. Intuicyjna definicja gramatyki pozbawionej kontekstu to gramatyka, którą można w całości wyrazić w BNF. Formalną definicję znajdziesz w artykule w Wikipedii na temat gramatyki niezależnej od kontekstu.

Rodzaje parserów

Istnieją 2 rodzaje parserów: parsery od góry i od dołu do góry. Intuicyjne wyjaśnienie polega na tym, że parsery odgórne analizują ogólną strukturę składni i próbują znaleźć dopasowanie do reguły. Parsery oddolne zaczynają od danych wejściowych i stopniowo przekształcają je w reguły składniowe, zaczynając od reguł niskiego poziomu, aż do spełnienia reguł wysokiego poziomu.

Zobaczmy, jak te 2 rodzaje parserów analizują przykład.

Parser odgórny zacznie działać od reguły wyższego poziomu: określi jako wyrażenie 2 + 3. Następnie identyfikuje 2 + 3 - 1 jako wyrażenie (proces identyfikowania wyrażenia ewoluuje, dopasowując się do innych reguł, ale punkt początkowy jest regułą najwyższego poziomu).

Parser oddolny będzie przeskanować dane wejściowe do momentu dopasowania reguły. Spowoduje to zastąpienie pasujących danych wejściowych regułą. Będzie to trwało do momentu zakończenia wprowadzania danych. Częściowo dopasowane wyrażenie jest umieszczane na stosie parsera.

Nakładaj Dane wejściowe
2 + 3 – 1
term + 3–1
operacja na scenie 3–1
wyrażenie – 1
operacja na wyrażeniach 1
wyrażenie -

Ten typ parsera oddolnego jest nazywany parserem Shift-reduce, ponieważ dane wejściowe są przesuwane w prawo (wyobraź sobie, że wskaźnik wskazuje pierwszy na początku danych wejściowych i przesuwa się w prawo) i jest stopniowo ograniczany do reguł składni.

Automatyczne generowanie parserów

Istnieją narzędzia do generowania parsera. Podajesz im gramatykę Twojego języka – reguły słownictwa i składni – a wtedy wygeneruje działający parser. Tworzenie parsera wymaga dogłębnej wiedzy na temat analizy składni, a ręczne utworzenie zoptymalizowanego parsera nie jest łatwe, dlatego generatory analiz mogą być bardzo przydatne.

WebKit korzysta z 2 dobrze znanych generatorów parserów: Flex do tworzenia lexera i Bison do tworzenia parsera (możesz natknąć się na nie z nazwami Lex i Yacc). Dane wejściowe Flex to plik zawierający definicje wyrażeń regularnych tokenów. Dane wejściowe Biona to reguły składni języka w formacie BNF.

Parser HTML

Zadaniem parsera HTML jest przekształcenie znaczników HTML w drzewo analizy.

Gramatyka HTML

Słownik i składnia języka HTML zostały określone w specyfikacjach opracowanych przez organizację W3C.

Jak zauważyliśmy we wprowadzeniu do analizy składni, składnię gramatyczną można zdefiniować formalnie za pomocą formatów takich jak BNF.

Niestety, wszystkie konwencjonalne tematy związane z parserami nie mają zastosowania do języka HTML (nie przywołałem ich dla zabawy, lecz zostaną wykorzystane przy analizowaniu arkuszy CSS i JavaScript). Języka HTML nie można łatwo zdefiniować za pomocą gramatyki pozbawionej kontekstu, której potrzebują parsery.

Istnieją formalny format definicji HTML – DTD (Document Type Definition) – ale nie jest to gramatyka pozbawiona kontekstu.

Na pierwszy rzut oka wydaje się to dziwne, ponieważ HTML jest raczej zbliżony do XML. Dostępnych jest wiele parserów XML. Istnieje odmiana kodu HTML w formacie XML – XHTML. Jaka jest różnica?

Różnica polega na tym, że metoda HTML „wybacza” – umożliwia pominięcie określonych tagów (które są następnie dodawane domyślnie) albo w niektórych przypadkach pominięcie tagów początkowych i końcowych itd. Mówiąc ogólnie, jest to „miękka” składnia w przeciwieństwie do sztywnej i wymagającej składni kodu XML.

Te pozornie nieistotne szczegóły zmieniają świat. Z jednej strony jest to główny powód, dla którego HTML jest tak popularny: wybacza błędy i ułatwia życie autorowi stron internetowych. Z drugiej strony utrudnia to pisanie formalnej gramatyki. Podsumowując, kodu HTML nie da się łatwo przeanalizować za pomocą konwencjonalnych parserów, ponieważ gramatyka nie jest zależna od kontekstu. Parsery XML nie mogą przeanalizować kodu HTML.

Plik DTD HTML

Definicja HTML jest w formacie DTD. Ten format służy do definiowania języków w rodzinie SGML. Format zawiera definicje wszystkich dozwolonych elementów, ich atrybuty i hierarchię. Jak już wspomnieliśmy, plik HTML DTD nie stanowi gramatyki pozbawionej kontekstu.

Jest kilka odmian DTD. Tryb ścisły jest zgodny wyłącznie ze specyfikacjami, ale inne tryby obsługują znaczniki używane przez przeglądarki w przeszłości. Celem jest uzyskanie zgodności wstecznej ze starszymi treściami. Aktualna dokładna wersja DTD jest dostępna tutaj: www.w3.org/TR/html4/strict.dtd

model DOM

Drzewo danych wyjściowych („drzewo analizy”) to drzewo elementów DOM i węzłów atrybutów. DOM to skrót od tytułu Document Object Model (Model obiektu dokumentu). Jest to prezentacja dokumentu HTML oraz interfejsu elementów HTML na zewnątrz, tak jak w języku JavaScript.

Główną częścią drzewa jest obiekt „Document”.

DOM ma ze znacznikami relację niemal jeden do jednego. Na przykład:

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

Te znaczniki zostaną przetłumaczone do następującego drzewa DOM:

Drzewo DOM znaczników przykładowych
Rys. 8. Drzewo DOM w przykładowych znacznikach

Podobnie jak w przypadku HTML, model DOM jest określany przez organizację W3C. Więcej informacji znajdziesz na stronie www.w3.org/DOM/DOMTR. Jest to ogólna specyfikacja umożliwiająca manipulowanie dokumentami. Konkretny moduł zawiera opis elementów HTML. Definicje pojęć HTML można znaleźć tutaj: www.w3.org/TR/2003/REC-DOM-Level-2-HTML-20030109/idl-definitions.html.

Gdy mówię, że drzewo zawiera węzły DOM, mam na myśli, że jest zbudowane z elementów, które implementują jeden z interfejsów DOM. Przeglądarki stosują konkretne implementacje, które mają też inne atrybuty używane przez przeglądarkę.

Algorytm analizy

Jak wspomnieliśmy w poprzednich sekcjach, kodu HTML nie można analizować za pomocą zwykłych parserów w dół i w górę.

Przyczyny są następujące:

  1. Wyrazisty charakter języka.
  2. Fakt, że przeglądarki mają tradycyjną tolerancję błędów, co pozwala obsługiwać znane przypadki nieprawidłowego użycia kodu HTML.
  3. Proces analizy jest powtórny. W przypadku innych języków źródło nie zmienia się podczas analizy, ale w języku HTML kod dynamiczny (np. elementy skryptu zawierające wywołania document.write()) może dodawać dodatkowe tokeny, więc proces analizy modyfikuje dane wejściowe.

Nie można użyć regularnych technik analizy, ponieważ przeglądarki tworzą niestandardowe parsery do analizy składni HTML.

Algorytm analizy został szczegółowo opisany w specyfikacji HTML5. Algorytm składa się z 2 etapów: tokenizacji i tworzenia drzewa.

Tokenizacja to analiza leksyka polegająca na analizowaniu danych wejściowych w tokeny. Tokeny HTML to między innymi tagi początkowe i końcowe, nazwy atrybutów i wartości atrybutów.

Tokenizator rozpoznaje token, przekazuje go konstruktorowi drzewa, a następnie przetwarza następny znak w celu rozpoznania następnego tokena i tak dalej, aż do końca danych wejściowych.

Procedura analizy HTML (pobrana ze specyfikacji HTML5)
Rys. 9. Przebieg analizy kodu HTML (pobrany ze specyfikacji HTML5)

Algorytm tokenizacji

Wynikiem algorytmu jest token HTML. Algorytm ten jest przedstawiany jako maszyna stanu. W każdym stanie jest używany co najmniej 1 znak ze strumienia wejściowego i następny stan jest aktualizowany odpowiednio do tych znaków. Na decyzję wpływa bieżący stan tokenizacji i stan konstrukcji drzewa. Oznacza to, że ten sam użyty znak zwróci różne wyniki dla prawidłowego stanu następnego w zależności od bieżącego stanu. Algorytm jest zbyt złożony, aby można go było w pełni opisać. Przyjrzyjmy się więc prostemu przykładowi, który pomoże nam zrozumieć zasadę.

Podstawowy przykład – tokenizacja poniższego kodu HTML:

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

Stan początkowy to „Stan danych”. Po napotkaniu znaku < stan zmienia się na „Tag otwarty”. Przejęcie znaku a-z powoduje utworzenie „Tokenu tagu początkowego”, który zmienia stan na „Stan nazwy tagu”. Pozostaniemy w tym stanie, dopóki znak > nie zostanie wykorzystany. Każdy znak zostanie dodany do nowej nazwy tokena. W naszym przypadku utworzony token jest tokenem html.

Po osiągnięciu tagu > wysyłany jest bieżący token, a stan zmienia się z powrotem na „Stan danych”. Tag <body> będzie traktowany w ten sam sposób. Do tej pory wysłano tagi html i body. Jesteśmy teraz z powrotem w sekcji „Stan danych”. Przejęcie znaku H z tabeli Hello world spowoduje utworzenie i wydanie tokena znaku. Będzie to trwało do osiągnięcia wartości < z </body>. Dla każdego znaku ciągu Hello world zostanie wygenerowany token znaków.

Teraz widzimy ponownie „Tag otwarty”. Wykorzystanie następnych danych wejściowych / spowoduje utworzenie end tag token i przejście do „Stanu nazwy tagu”. Pozostaniemy w tym stanie do osiągnięcia wartości >.Następnie zostanie wysłany nowy token tagu i wrócimy do stanu „Stan danych”. Dane wejściowe </html> będą traktowane tak jak poprzednio.

Tokenizacja przykładowych danych wejściowych
Rys. 10. Tokenizacja przykładowych danych wejściowych

Algorytm konstrukcji drzew

Po utworzeniu parsera tworzony jest obiekt Document. Podczas etapu budowy drzewa drzewo DOM z dokumentem w jego katalogu głównym zostanie zmodyfikowane i zostaną do niego dodane elementy. Każdy węzeł wygenerowany przez tokenizator zostanie przetworzony przez konstruktor drzewa. Specyfikacja określa, który element DOM jest odpowiedni dla każdego tokena i zostanie utworzony dla danego tokena. Element zostanie dodany do drzewa DOM, a także do stosu otwartych elementów. Ten stos jest używany do korygowania niezgodności zagnieżdżenia i niezamkniętych tagów. Algorytm ten jest też nazywany maszyną stanu. Stany są nazywane „trybami wstawiania”.

Przyjrzyjmy się procesowi tworzenia drzewa dla przykładowych danych wejściowych:

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

Dane wejściowe etapu tworzenia drzewa to sekwencja tokenów z etapu tokenizacji. Pierwszym z nich jest „tryb początkowy”. Odebranie tokena „html” spowoduje przejście do trybu „przed html” i ponowne przetworzenie tokena w tym trybie. Spowoduje to utworzenie elementu HTMLHTMLElement, który zostanie dołączony do głównego obiektu Document.

Stan zostanie zmieniony na "before head". Następnie otrzymuje się token „body”. Element HTMLHeadElement zostanie utworzony domyślnie. Nie mamy jednak tokena „head” – zostanie on dodany do drzewa.

Przechodzimy teraz do trybu „w głowie”, a potem do trybu „za głową”. Token treści jest przetwarzany ponownie, tworzony i wstawiony element HTMLBodyElement, a tryb jest przenoszony do "in body".

Odebrano tokeny znaków z ciągu „Hello world”. Pierwszy z nich spowoduje utworzenie i wstawienie węzła „Tekst”, a pozostałe znaki zostaną dołączone do tego węzła.

Odebranie tokena końcowego treści spowoduje przeniesienie do trybu "after body". Otrzymasz teraz tag końcowy HTML, który przeniesie nas do trybu „po tekście”. Odebranie końca tokena pliku spowoduje zakończenie analizy.

Konstrukcja drzewa z przykładowym kodem HTML.
Rysunek 11. Konstrukcja drzewa przykładowego kodu HTML

Działania po zakończeniu analizy

Na tym etapie przeglądarka oznaczy dokument jako interaktywny i rozpocznie analizowanie skryptów, które są w trybie „odroczonym”, czyli tych, które powinny zostać wykonane po przeanalizowaniu dokumentu. Stan dokumentu zostanie zmieniony na „zakończony” i uruchomione zostanie zdarzenie „load”.

Pełne algorytmy tokenizacji i konstruowania drzew znajdziesz w specyfikacji HTML5.

Tolerancja błędów przeglądarek

Na stronie HTML nigdy nie występuje błąd „Nieprawidłowa składnia”. Przeglądarki poprawiają nieprawidłową treść i kontynuują procedurę.

Weźmy na przykład ten kod HTML:

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

Musiałem naruszyć około miliona reguł („mójtag” nie jest tagiem standardowym, nieprawidłowe zagnieżdżenie elementów „p” i „div” itp.), ale przeglądarka nadal wyświetla ten tag prawidłowo i nie zgłasza skarg. Dlatego duża część kodu parsera naprawia błędy autorów kodu HTML.

Obsługa błędów w przeglądarkach jest dość spójna, ale co zaskakujące, nie ma jej w specyfikacji HTML. To coś, co rozwinęło się w przeglądarkach na przestrzeni lat. Istnieją znane nieprawidłowe konstrukcje HTML powtórzone w wielu witrynach, a przeglądarki starają się naprawić je w sposób zgodny z innymi przeglądarkami.

Specyfikacja HTML5 definiuje niektóre z tych wymagań. (WebKit dobrze to podsumowuje w komentarzu na początku klasy parsera HTML).

Parser analizuje tokenizowane dane wejściowe w dokumencie, tworząc drzewo dokumentu. Jeśli dokument jest poprawnie sformułowany, można go łatwo przeanalizować.

Niestety musimy obsługiwać wiele dokumentów HTML, które mają nieprawidłowy format, dlatego parser musi tolerować błędy.

Musisz usunąć co najmniej te warunki błędu:

  1. Dodawany element jest jawnie zabroniony wewnątrz jakiegoś tagu zewnętrznego. W tym przypadku należy zamknąć wszystkie tagi do tego, który zabrania danego elementu, a następnie dodać go później.
  2. Nie możemy dodawać elementu bezpośrednio. Możliwe, że osoba, która pisze dokument, zapomniała o pewnym tagu w trakcie tego procesu (lub tag między tagami jest opcjonalny). Mogło tak być w przypadku następujących tagów: HTML HEAD BODY TBODY TR TD LI (czy jakieś nie zostało pominięte?).
  3. Chcemy dodać blokowy element wewnątrz elementu wbudowanego. Zamknij wszystkie wbudowane elementy aż do elementu, który znajduje się wyżej na liście.
  4. Jeśli to nie pomoże, zamknij elementy, dopóki nie będziemy mogli ich dodać, lub zignoruj tag.

Przyjrzyjmy się kilku przykładom tolerancji błędów WebKit:

</br> zamiast <br>

Niektóre strony używają </br> zamiast <br>. Aby zapewnić zgodność z IE i Firefox, WebKit traktuje to tak jak <br>.

Kod:

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

Pamiętaj, że obsługa błędów ma charakter wewnętrzny – nie będzie widoczna dla użytkownika.

Zbędny tabela

Zbędna tabela to tabela w innej tabeli, ale nie w komórce tabeli.

Na przykład:

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

WebKit zmieni hierarchię na dwie tabele równorzędne:

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

Kod:

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

Bieżąca zawartość elementu WebKit jest wykorzystywana w taki sposób, aby wyskakiwać tabelę wewnętrzną z zewnętrznego stosu tabel. Tabele będą teraz elementami równorzędnymi.

Zagnieżdżone elementy formularzy

Jeśli użytkownik wstawi formularz w innym formularzu, drugi formularz zostanie zignorowany.

Kod:

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

Zbyt głęboka hierarchia tagów

Ten komentarz mówi sam za siebie.

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

Niewłaściwie umieszczone tagi HTML lub tagi treści

Ponownie – ten komentarz mówi sam za siebie.

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

Dlatego autorzy stron internetowych powinni pisać poprawnie sformatowany kod HTML, chyba że chcesz pokazywać go jako przykład we fragmencie kodu tolerancji błędów WebKit.

Analiza CSS

Pamiętasz pojęcia analizy przedstawione we wstępie? W przeciwieństwie do języka HTML CSS to gramatyka bez kontekstu i może być analizowana za pomocą typów parserów opisanych we wprowadzeniu. W rzeczywistości specyfikacja CSS definiuje gramatyka leksyka i składnię CSS.

Przyjrzyjmy się kilku przykładom:

Gramatyka leksyka (słownictwo) jest definiowana przez wyrażenia regularne dla każdego tokena:

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” to skrót od identyfikatora, np. nazwy klasy. „name” to identyfikator elementu (oznaczony znakiem „#”)

Gramatyka składni jest opisana w BNF.

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

Wyjaśnienie:

Zestaw reguł ma taką strukturę:

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

div.error i a.error to selektory. Część wewnątrz nawiasów klamrowych zawiera reguły stosowane przez ten zestaw reguł. Struktura ta jest oficjalnie zdefiniowana w tej definicji:

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

Oznacza to, że zestaw reguł jest selektorem lub opcjonalnie liczbą selektorów rozdzielonych przecinkami i spacjami (S to spacja). Zestaw reguł zawiera nawiasy klamrowe, a w nich deklarację lub opcjonalnie deklaracje rozdzielone średnikiem. „deklaracja” i „selektor” będą zdefiniowane w poniższych definicjach BNF.

Parser CSS WebKit

WebKit wykorzystuje generatory parserów Flex i Bison do automatycznego tworzenia parserów na podstawie plików gramatyki CSS. Jak pamiętasz ze wstępu do parsera, Bison tworzy parser typu Shift-reduce od dołu do góry. Firefox używa ręcznego parsera odgórnego. W obu przypadkach każdy plik CSS jest analizowany jako obiekt StyleSheet. Każdy obiekt zawiera reguły CSS. Obiekty reguł CSS zawierają selektory i deklaracje oraz inne obiekty odpowiadające gramatyce CSS.

Analizuję kod CSS.
Rysunek 12. Analiza składni CSS

Przetwarzanie zamówienia skryptów i arkuszy stylów

Skrypty

Model sieci jest synchroniczny. Autorzy oczekują, że skrypty będą analizowane i wykonywane natychmiast, gdy parser dotrze do tagu <script>. Analiza dokumentu jest zatrzymywana do czasu wykonania skryptu. Jeśli skrypt jest zewnętrzny, zasób musi najpierw zostać pobrany z sieci. Procedura ta również jest wykonywana synchronicznie i analiza zatrzymuje się do czasu pobrania zasobu. Ten model był używany przez wiele lat i został określony w specyfikacjach HTML4 i 5. Autorzy mogą dodać do skryptu atrybut „defer”, dzięki czemu narzędzie to nie zatrzyma analizy dokumentu i zostanie wykonane po przeanalizowaniu dokumentu. HTML5 dodaje opcję oznaczającą skrypt jako asynchroniczny, co umożliwia analizowanie i wykonywanie go w innym wątku.

Analiza spekulacyjna

Optymalizację dokonują zarówno przeglądarki WebKit, jak i Firefox. Podczas wykonywania skryptów inny wątek analizuje pozostałą część dokumentu i sprawdza, jakie inne zasoby należy wczytać z sieci, a następnie je wczytuje. W ten sposób zasoby mogą być ładowane przy połączeniach równoległych, co zwiększa ogólną szybkość. Uwaga: parser spekulacyjny analizuje tylko odwołania do zewnętrznych zasobów, takich jak zewnętrzne skrypty, arkusze stylów i obrazy – nie modyfikuje drzewa DOM, które pozostaje głównemu parserowi.

Arkusze stylów

Arkusze stylów mają natomiast inny model. W zasadzie wydaje się, że skoro arkusze stylów nie zmieniają drzewa DOM, nie ma powodu, by czekać na nie i zatrzymać analizę dokumentu. Występuje jednak problem ze skryptami z prośbą o podanie informacji o stylu na etapie analizy dokumentu. Jeśli styl nie został jeszcze wczytany i przeanalizowany, skrypt otrzyma nieprawidłowe odpowiedzi, co najwyraźniej powodowało wiele problemów. Wydaje się, że jest to skrajny przypadek, ale jest to dość powszechne. Firefox blokuje wszystkie skrypty, gdy istnieje arkusz stylów, który jest nadal ładowany i analizowany. WebKit blokuje skrypty tylko wtedy, gdy próbują uzyskać dostęp do właściwości stylu, na które mogą mieć wpływ niewczytane arkusze stylów.

Renderowanie konstrukcji drzewa

Podczas tworzenia drzewa DOM przeglądarka tworzy kolejne drzewo – drzewo renderowania. To drzewo zawiera elementy wizualne w kolejności, w jakiej będą wyświetlane. Jest wizualną reprezentacją dokumentu. To drzewo umożliwia pokazanie treści we właściwej kolejności.

Elementy w drzewie renderowania nazywają Firefox „ramkami”. WebKit używa terminu „renderer” (mechanizm renderowania), czyli renderowania obiektu.

Renderer wie, jak rozmieścić i malować siebie oraz jej elementy podrzędne.

Klasa RenderObject w WebKit, klasa bazowa mechanizmów renderowania, ma taką definicję:

class RenderObject{
  virtual void layout();
  virtual void paint(PaintInfo);
  virtual void rect repaintRect();
  Node* node;  //the DOM node
  RenderStyle* style;  // the computed style
  RenderLayer* containgLayer; //the containing z-index layer
}

Każdy mechanizm renderowania to prostokątny obszar, który zwykle odpowiada polowi CSS węzła, zgodnie ze specyfikacją CSS2. Zawiera dane geometryczne, takie jak szerokość, wysokość i położenie.

Na typ pola wpływa wartość „display” atrybutu stylu, który ma zastosowanie do węzła (patrz sekcja obliczanie stylów). Oto kod WebKit, który pozwala określić typ mechanizmu renderowania, który powinien zostać utworzony dla węzła DOM, zgodnie z atrybutem displayowym:

RenderObject* RenderObject::createObject(Node* node, RenderStyle* style)
{
    Document* doc = node->document();
    RenderArena* arena = doc->renderArena();
    ...
    RenderObject* o = 0;

    switch (style->display()) {
        case NONE:
            break;
        case INLINE:
            o = new (arena) RenderInline(node);
            break;
        case BLOCK:
            o = new (arena) RenderBlock(node);
            break;
        case INLINE_BLOCK:
            o = new (arena) RenderBlock(node);
            break;
        case LIST_ITEM:
            o = new (arena) RenderListItem(node);
            break;
       ...
    }

    return o;
}

Pod uwagę brany jest też typ elementu, na przykład elementy sterujące formularza i tabele mają specjalne ramki.

W WebKit, jeśli element chce utworzyć specjalny mechanizm renderowania, zastąpi on metodę createRenderer(). Mechanizmy renderowania wskazują style obiektów, które zawierają informacje niegeometryczne.

Relacja drzewa renderowania do drzewa DOM

Mechanizmy renderowania odpowiadają elementom DOM, ale relacje nie różnią się od siebie. Niewizualne elementy DOM nie będą wstawiane do drzewa renderowania. Przykładem jest element „head”. Również elementy, których wyświetlana wartość została przypisana do wartości „brak”, nie pojawią się w drzewie (podczas gdy elementy z widocznością „ukrytą” pojawią się w drzewie).

Istnieją elementy DOM, które odpowiadają kilku obiektom wizualnym. Są to zazwyczaj elementy o złożonej strukturze, których nie da się opisać jednym prostokątem. Na przykład element „select” ma 3 mechanizmy renderowania: jeden dla obszaru wyświetlania, jeden dla pola listy i jeden dla przycisku. Gdy tekst jest podzielony na wiele wierszy, bo szerokość jednego wiersza jest niewystarczająca, nowe wiersze są dodawane jako dodatkowe mechanizmy renderowania.

Innym przykładem wielu mechanizmów renderowania jest uszkodzony kod HTML. Zgodnie ze specyfikacją CSS element wbudowany może zawierać wyłącznie elementy blokowe lub tylko elementy wbudowane. W przypadku treści mieszanej zostaną utworzone anonimowe mechanizmy renderowania bloków, które pakują elementy wbudowane.

Niektóre obiekty renderowania odpowiadają węzłowi DOM, ale nie w tym samym miejscu w drzewie. Elementy swobodne i absolutnie umieszczone elementy nie są płynne, umiejscowione w innej części drzewa i zmapowane na rzeczywistą klatkę. Ramka zastępcza powinna znajdować się tam, gdzie powinny.

Drzewo renderowania i odpowiednie drzewo DOM.
Rys. 13. Drzewo renderowania i odpowiadające im drzewo DOM. „Widoczny obszar” to początkowy blok zawierający go. W WebKit będzie to obiekt „RenderView”.

Procedura tworzenia drzewa

W przeglądarce Firefox prezentacja jest rejestrowana jako detektor aktualizacji DOM. Prezentacja przekazuje tworzenie ramki do FrameConstructor, a konstruktor określa styl (patrz Obliczanie stylu) i tworzy ramkę.

W WebKit proces rozpoznawania stylu i tworzenia mechanizmu renderowania nazywa się „załącznikiem”. Każdy węzeł DOM ma metodę „dołącz”. Przyłącze jest synchroniczne, a wstawianie węzłów do drzewa DOM wywołuje nową metodę „attach” dla nowego węzła.

Przetwarzanie tagów HTML i body powoduje utworzenie głównego korzenia drzewa renderowania. Obiekt renderowania głównego odpowiada temu, co specyfikacja CSS wywołuje blok zawierający: najwyższy blok, który zawiera wszystkie pozostałe bloki. Jej wymiary to widoczny obszar, czyli obszar wyświetlania okna przeglądarki. W przeglądarce Firefox jest to ViewPortFrame, a w silniku WebKit – RenderView. To jest obiekt renderowania, na który wskazuje dokument. Pozostała część drzewa powinna mieć postać wstawiania węzłów DOM.

Zobacz specyfikację CSS2 dla modelu przetwarzania.

Obliczanie stylu

Aby utworzyć drzewo renderowania, musisz obliczyć właściwości wizualne każdego renderowanego obiektu. Odbywa się to przez obliczenie właściwości stylu każdego elementu.

Ten styl obejmuje arkusze stylów z różnych źródeł, wbudowane elementy stylów i właściwości wizualne w kodzie HTML (np. właściwość „bgcolor”). Później jest przetłumaczony na pasujące właściwości stylu CSS.

Początki arkuszy stylów to domyślne arkusze stylów przeglądarki, arkusze stylów dostarczone przez autora strony i arkusze stylów użytkownika. Są to arkusze stylów udostępniane przez użytkownika przeglądarki (przeglądarki umożliwiają definiowanie ulubionych stylów). Na przykład w przeglądarce Firefox należy to zrobić, umieszczając arkusz stylów w folderze „Profil przeglądarki Firefox”).

Obliczanie stylu wiąże się z kilkoma trudnościami:

  1. Dane stylu to bardzo duży obiekt, który zawiera liczne właściwości stylu, co może powodować problemy z pamięcią.
  2. Znalezienie reguł pasujących do każdego elementu może powodować problemy z wydajnością, jeśli nie jest on zoptymalizowany. Przemierzanie całej listy reguł każdego elementu w celu znalezienia dopasowań to uciążliwe zadanie. Selektory mają złożoną strukturę, co sprawia, że proces dopasowywania rozpoczyna się na obiecującej ścieżce, która jest bezużyteczna i trzeba spróbować innej metody.

    Na przykład ten selektor złożony:

    div div div div{
    ...
    }
    

    Oznacza to, że reguły mają zastosowanie do elementu <div>, który jest elementem potomnym 3 elementów div. Załóżmy, że chcesz sprawdzić, czy reguła ma zastosowanie do danego elementu <div>. Wybierasz w górę drzewa konkretną ścieżkę do sprawdzenia. Być może trzeba będzie przeszukać drzewo węzłów, żeby zobaczyć, że są tylko 2 elementy div, a reguła nie ma zastosowania. Następnie musisz wypróbować inne ścieżki w drzewie.

  3. Stosowanie reguł obejmuje dość złożone reguły kaskadowe, które definiują hierarchię reguł.

Zobaczmy, jak przeglądarki reagują na te problemy:

Udostępnianie danych o stylu

Węzły WebKit odwołują się do obiektów stylu (RenderStyle). W pewnych warunkach te obiekty mogą być udostępniane przez węzły. Węzły to rodzeństwo lub kuzyni, a także:

  1. Elementy muszą być w tym samym stanie myszy (np.jeden nie może znajdować się w :hover, a drugi nie).
  2. Żaden z elementów nie powinien mieć identyfikatora
  3. Nazwy tagów powinny być takie same
  4. Atrybuty klasy powinny być takie same
  5. Zbiór zmapowanych atrybutów musi być identyczny
  6. Stany połączenia muszą być takie same
  7. Stany zaznaczenia muszą być takie same
  8. selektory atrybutów nie powinny wpływać na żaden z nich – gdy ma to wpływ na dopasowanie selektora, które korzysta z selektora atrybutu na dowolnym miejscu w selektorze;
  9. Elementy nie mogą mieć atrybutów stylu wbudowanego.
  10. Nie mogą być używane żadne selektory równorzędne. WebCore po prostu generuje przełącznik globalny, gdy napotka selektor elementów równorzędnych, i wyłącza udostępnianie stylów dla całego dokumentu, jeśli są one dostępne. Obejmuje to selektor + oraz selektory takie jak :pierwsze-dziecko i :last-child.

Drzewo reguł przeglądarki Firefox

W przeglądarce Firefox są dostępne 2 dodatkowe drzewa ułatwiające obliczanie stylu: drzewo reguł i drzewo kontekstu stylu. WebKit również ma obiekty stylów, ale nie są one przechowywane w drzewie, takim jak drzewo kontekstu stylu, tylko węzeł DOM wskazuje odpowiedni styl.

Drzewo kontekstu w stylu Firefoxa.
Rysunek 14. Drzewo kontekstu w stylu przeglądarki Firefox.

Konteksty stylów zawierają wartości końcowe. Aby je obliczyć, stosuje się wszystkie reguły dopasowania we właściwej kolejności i wykonuje manipulacje, które przekształcają je z wartości logicznych na konkretne. Jeśli na przykład wartość logiczna to procent ekranu, zostanie ona obliczona i przekształcona na jednostki bezwzględne. Pomysł z drzewem reguł jest bardzo sprytny. Umożliwia udostępnianie tych wartości między węzłami, aby uniknąć ich ponownego obliczania. W ten sposób zaoszczędzisz też miejsce.

Wszystkie dopasowane reguły są przechowywane w drzewie. Dolne węzły w ścieżce mają wyższy priorytet. Drzewo zawiera wszystkie znalezione ścieżki dopasowań reguł. Przechowywanie reguł odbywa się leniwie. Drzewo nie jest obliczane na początku dla każdego węzła, ale za każdym razem, gdy trzeba obliczyć styl węzła, obliczone ścieżki są dodawane do drzewa.

Chodzi o to, by ścieżki po drzewie były widoczne jako słowa w leksykonie. Załóżmy, że obliczyliśmy już to drzewo reguł:

Obliczone drzewo reguł
Rys. 15. Obliczone drzewo reguł

Załóżmy, że musimy dopasować reguły dla innego elementu w drzewie treści i ustalić, że pasujące reguły (we właściwej kolejności) to B-E-I. Ta ścieżka jest już w drzewie, ponieważ została już obliczona ścieżka A-B-E-I-L. Będziemy teraz mieli mniej pracy.

Zobaczmy, jak to drzewo oszczędza nam pracę.

Podziel na struct

Konteksty stylów są podzielone na struktury typu struct. Te struktury zawierają informacje o stylu dla określonej kategorii, takiej jak obramowanie lub kolor. Wszystkie właściwości elementu struct są dziedziczone lub nie. Właściwości dziedziczone to właściwości, które są dziedziczone z elementu nadrzędnego, chyba że zostały przez niego zdefiniowane. Właściwości niedziedziczone (nazywane właściwościami „reset”) korzystają z wartości domyślnych, jeśli nie są zdefiniowane.

Drzewo pomaga nam przez buforowanie w nim całych struktur (zawierających obliczone wartości końcowe). Chodzi o to, że jeśli dolny węzeł nie dostarczył definicji elementu struct, można użyć konstrukcji przechowywanej w pamięci podręcznej w górnym węźle.

Obliczanie kontekstu stylu za pomocą drzewa reguł

Podczas obliczania kontekstu stylu dla określonego elementu najpierw obliczamy ścieżkę w drzewie reguł lub używamy istniejącej. Następnie zaczynamy stosować reguły w ścieżce, aby wypełnić struktury w nowym kontekście stylu. Zaczynamy od dolnego węzła ścieżki, czyli tego, który ma najwyższy priorytet (zwykle jest to najbardziej szczegółowy selektor), i przemierzamy drzewo aż do momentu, gdy obiekt struct będzie pełny. Jeśli nie ma specyfikacji elementu struct w tym węźle reguły, możemy znacznie przeprowadzić optymalizację – przechodzimy w górę drzewa, aż znajdziemy węzeł, który w pełni go określa i wskazuje na niego – to najlepsza optymalizacja – cały element struct jest współużytkowany. Oszczędza to obliczanie wartości końcowych i pamięci.

Jeśli znajdziemy częściowe definicje, wejdziemy w górę drzewa, aż element struct zostanie uzupełniony.

Jeśli nie znajdziemy żadnych definicji elementu struct, to jeśli jest on typu „dziedziczonego”, wskazujemy strukturę elementu nadrzędnego w drzewie kontekstu. W tym przypadku również udało nam się udostępnić pliki struct. Jeśli jest to struktura resetowania, używane są wartości domyślne.

Jeśli najbardziej zawężony węzeł dodaje wartości, musimy wykonać dodatkowe obliczenia, by przekształcić go w wartości rzeczywiste. Następnie zapisujemy wynik w pamięci podręcznej w węźle drzewa, aby mógł być używany przez podrzędne.

Jeśli element ma elementy równorzędne lub równorzędne, które wskazuje ten sam węzeł drzewa, można udostępniać między nimi cały kontekst stylu.

Oto przykład: Załóżmy, że mamy kod HTML,

<html>
  <body>
    <div class="err" id="div1">
      <p>
        this is a <span class="big"> big error </span>
        this is also a
        <span class="big"> very  big  error</span> error
      </p>
    </div>
    <div class="err" id="div2">another error</div>
  </body>
</html>

Oraz te reguły:

div {margin: 5px; color:black}
.err {color:red}
.big {margin-top:3px}
div span {margin-bottom:4px}
#div1 {color:blue}
#div2 {color:green}

Aby to uprościć, załóżmy, że musimy wypełnić tylko 2 struktury: kolor i strukturę marginesów. Struktura kolorów zawiera tylko 1 element: kolor Struktura marginesów obejmuje cztery boki.

Drzewo reguł będzie wyglądać tak (węzły są oznaczone nazwą węzła, czyli numerem reguły, na którą wskazują):

Drzewo reguł
Rys. 16. Drzewo reguł

Drzewo kontekstu będzie wyglądać tak (nazwa węzła: węzeł reguły, na który wskazuje):

Drzewo kontekstu.
Rys. 17. Drzewo kontekstu

Załóżmy, że analizujemy kod HTML i docieramy do drugiego tagu <div>. Musimy utworzyć kontekst stylu dla tego węzła i wypełnić jego struktury stylów.

Dopasujemy reguły i odkryjemy, że reguły, które spełniają kryteria <div>, to: 1, 2 i 6. Oznacza to, że w drzewie istnieje już ścieżka, z której może korzystać nasz element. Musimy tylko dodać do niej kolejny węzeł dla reguły 6 (węzeł F w drzewie reguł).

Utworzymy kontekst stylu i umieścimy go w drzewie kontekstu. Nowy kontekst stylu będzie wskazywać węzeł F w drzewie reguł.

Teraz musimy wypełnić struktury stylów. Zaczniemy od wypełnienia pól struct marginesów. Ponieważ ostatni węzeł reguły (F) nie dodaje elementów do struktury marginesów, możemy wchodzić w górę drzewa, dopóki nie znajdziemy zapisanej w pamięci podręcznej struktury obliczonej we wcześniejszym wstawieniu węzła i jej użyjesz. Znajdziemy ją w węźle B, który jest najwyższym węzłem, który określa reguły marginesów.

Mamy definicję struktury kolorów, więc nie możemy użyć struktury w pamięci podręcznej. Ponieważ kolor ma jeden atrybut, nie musimy przechodzić w górę drzewa, aby uzupełnić pozostałe atrybuty. Obliczymy wartość końcową (przekonwertuj ciąg znaków na RGB itp.) i umieścimy w pamięci podręcznej obliczoną strukturę w tym węźle.

Praca nad drugim elementem <span> jest jeszcze łatwiejsza. Dopasowujemy reguły i dochodzimy do wniosku, że wskazuje ona regułę G, tak jak poprzedni rozpiętość. Ponieważ mamy elementy równorzędne, które wskazują ten sam węzeł, możemy udostępnić cały kontekst stylu i wskazać tylko kontekst poprzedniego spanu.

W przypadku struktur zawierających reguły dziedziczone z elementu nadrzędnego zapisywanie w pamięci podręcznej odbywa się w drzewie kontekstu (atrybut „color” jest w rzeczywistości dziedziczony, ale Firefox traktuje ją jako resetowaną i przechowuje w pamięci podręcznej w drzewie reguł).

Jeśli na przykład dodamy reguły czcionek w akapicie:

p {font-family: Verdana; font size: 10px; font-weight: bold}

Element akapitu, który jest elementem podrzędnym elementu div w drzewie kontekstu, mógł mieć taką samą strukturę czcionki jak element nadrzędny. Dzieje się tak, jeśli w akapicie nie określono żadnych reguł dotyczących czcionek.

W WebKit, który nie ma drzewa reguł, dopasowane deklaracje są przeszukiwane 4 razy. Stosowane są najpierw mniej ważne właściwości o wysokim priorytecie (właściwości, które powinny być zastosowane w pierwszej kolejności, ponieważ inne zależą od nich, np. sieć reklamowa), następnie reguły o wysokim priorytecie „ważne”, a następnie „zwykłe „nieistotne” i reguły o normalnym priorytecie. Oznacza to, że właściwości, które pojawiają się wielokrotnie, będą rozpoznawane zgodnie z prawidłową kolejnością kaskady. Ostatnie wygrywają.

Podsumowując: udostępnienie obiektów stylu (całych lub niektórych znajdujących się w nich elementów) rozwiązuje problemy 1 i 3. Drzewo reguł przeglądarki Firefox pomaga też stosować właściwości we właściwej kolejności.

Manipulowanie zasadami, aby ułatwić sobie dopasowanie

Istnieje kilka źródeł reguł stylu:

  1. Reguły CSS w zewnętrznych arkuszach stylów lub elementach stylów.css p {color: blue}
  2. Atrybuty stylu wbudowanego, takie jak html <p style="color: blue" />
  3. Atrybuty wizualne HTML (które są zmapowane na odpowiednie reguły stylu) html <p bgcolor="blue" /> Dwa ostatnie można łatwo dopasować do elementu, ponieważ jest on właścicielem atrybutów stylu, a atrybuty HTML można zmapować za pomocą tego elementu jako klucza.

Jak już wspomnieliśmy w punkcie 2, dopasowywanie reguł CSS może być trudniejsze. Aby rozwiązać ten problem, reguły są tak modyfikowane, że dostęp do nich jest łatwiejszy.

Po przeanalizowaniu arkusza stylów reguły są dodawane do jednej z kilku map skrótów, zgodnie z selektorem. Istnieją mapy według identyfikatora, nazwy klasy, nazwy tagu i ogólnej mapy wszystkich elementów, które nie należą do tych kategorii. Jeśli selektorem jest identyfikator, reguła zostanie dodana do mapy identyfikatorów, a jeśli jest to klasa, zostanie dodana do mapy klasy itp.

Takie manipulacje znacznie ułatwiają dopasowywanie reguł. Nie musisz sprawdzać każdej deklaracji: możemy wyodrębnić z map odpowiednie reguły dla elementu. Optymalizacja eliminuje ponad 95% reguł, dzięki czemu nie trzeba nawet ich uwzględniać podczas dopasowywania(4.1).

Przyjrzyjmy się na przykład następującym regułom stylu:

p.error {color: red}
#messageDiv {height: 50px}
div {margin: 5px}

Pierwsza reguła zostanie wstawiona do mapy zajęć. Drugi w mapie identyfikatorów, a trzeci w mapie tagów.

Dla poniższego fragmentu HTML:

<p class="error">an error occurred</p>
<div id=" messageDiv">this is a message</div>

Najpierw spróbujemy znaleźć reguły dla elementu p. Mapa klasy będzie zawierać klucz „error” (błąd), pod którym znajduje się reguła „p.error”. Element div będzie miał odpowiednie reguły w mapie identyfikatorów (klucz to identyfikator) i mapie tagu. Teraz musisz już tylko sprawdzić, które reguły wyodrębnione przez klucze są naprawdę zgodne.

Jeśli na przykład reguła dotycząca elementu div to:

table div {margin: 5px}

Zostanie on wyodrębniony z mapy tagów, ponieważ klucz jest selektorem znajdującym się najbardziej po prawej stronie, ale nie pasuje do elementu div, który nie ma elementu nadrzędnego tabeli.

Tę czynność robią zarówno WebKit, jak i Firefox.

Kolejność kaskadowa arkusza stylów

Obiekt stylu ma właściwości odpowiadające każdemu atrybutowi wizualnemu (wszystkie atrybuty CSS, ale bardziej ogólne). Jeśli żadna z pasujących reguł nie określa właściwości, niektóre właściwości mogą być dziedziczone przez obiekt stylu elementu nadrzędnego. Inne właściwości mają wartości domyślne.

Problem zaczyna się, gdy istnieje więcej niż 1 definicja – tutaj do rozwiązania problemu dochodzi w kolejności kaskadowej.

Deklaracja właściwości stylu może pojawić się w kilku arkuszach stylów i kilka razy w arkuszu. Oznacza to, że kolejność stosowania reguł jest bardzo ważna. Jest to tzw. zamówienie „kaskadowe”. Zgodnie ze specyfikacją CSS2 kolejność kaskadowa wynosi (od najmniejszej do najwyższej):

  1. Deklaracje przeglądarki
  2. Zwykłe deklaracje użytkownika
  3. Normalne deklaracje autora
  4. Utwórz ważne deklaracje
  5. Ważne deklaracje użytkownika

Deklaracje przeglądarki są najmniej ważne, a użytkownik zastępuje autora tylko wtedy, gdy deklaracja została oznaczona jako ważna. Deklaracje z tą samą kolejnością będą sortowane według specyficzności, a następnie według kolejności ich określania. Wizualne atrybuty HTML są przekształcane w odpowiednie deklaracje CSS . Są traktowane jak reguły autorskie o niskim priorytecie.

Zgodność ze specyfikacją

Szczegółowość selektora jest zdefiniowana w specyfikacji CSS2 w ten sposób:

  1. count 1, jeśli deklaracja, z której pochodzi, jest atrybutem „style”, a nie reguła z selektorem; 0 w przeciwnym razie (= a)
  2. zliczać atrybuty identyfikatora w selektorze (= b)
  3. policz inne atrybuty i pseudoklasy w selektorze (= c)
  4. policzyć liczbę nazw elementów i pseudoelementów w selektorze (= d)

Połączenie 4 cyfr a-b-c-d (w systemie liczbowym o dużej podstawie) daje precyzję.

Podstawa liczbowa, której należy użyć, jest określana na podstawie największej liczby elementów w jednej z kategorii.

Na przykład w przypadku a=14 możesz użyć systemu szesnastkowego w systemie szesnastkowym. W mało prawdopodobnym przypadku, gdzie a=17, potrzebujesz 17-cyfrowej bazy liczbowej. Sytuacja później może się zdarzyć, jeśli użyjesz takiego selektora: html body div div p... (17 tagów w selektorze... mało prawdopodobne).

Oto kilka przykładów:

 *             {}  /* a=0 b=0 c=0 d=0 -> specificity = 0,0,0,0 */
 li            {}  /* a=0 b=0 c=0 d=1 -> specificity = 0,0,0,1 */
 li:first-line {}  /* a=0 b=0 c=0 d=2 -> specificity = 0,0,0,2 */
 ul li         {}  /* a=0 b=0 c=0 d=2 -> specificity = 0,0,0,2 */
 ul ol+li      {}  /* a=0 b=0 c=0 d=3 -> specificity = 0,0,0,3 */
 h1 + *[rel=up]{}  /* a=0 b=0 c=1 d=1 -> specificity = 0,0,1,1 */
 ul ol li.red  {}  /* a=0 b=0 c=1 d=3 -> specificity = 0,0,1,3 */
 li.red.level  {}  /* a=0 b=0 c=2 d=1 -> specificity = 0,0,2,1 */
 #x34y         {}  /* a=0 b=1 c=0 d=0 -> specificity = 0,1,0,0 */
 style=""          /* a=1 b=0 c=0 d=0 -> specificity = 1,0,0,0 */

Sortowanie reguł

Po dopasowaniu reguł są one sortowane według reguł kaskadowych. WebKit używa sortowania bąbelków w przypadku małych list i scalania w przypadku dużych. WebKit implementuje sortowanie, zastępując operator > w przypadku reguł:

static bool operator >(CSSRuleData& r1, CSSRuleData& r2)
{
    int spec1 = r1.selector()->specificity();
    int spec2 = r2.selector()->specificity();
    return (spec1 == spec2) : r1.position() > r2.position() : spec1 > spec2;
}

Proces stopniowy

WebKit używa flagi wskazującej, czy zostały wczytane wszystkie arkusze stylów najwyższego poziomu (w tym @importy). Jeśli styl nie zostanie w pełni wczytany podczas dołączania, używane są elementy zastępcze, które są oznaczone w dokumencie. Zostaną one ponownie obliczone po wczytaniu arkuszy stylów.

Układ

Po utworzeniu i dodaniu do drzewa mechanizmu renderowania nie ma on pozycji ani rozmiaru. Obliczanie tych wartości jest nazywane układem lub przeformatowaniem.

HTML wykorzystuje model układu oparty na przepływie, co oznacza, że najczęściej można obliczyć geometrię w ramach jednego przebiegu. Późniejsze elementy „w przepływie” zwykle nie wpływają na geometrię elementów, które znajdują się wcześniej na „przepływie”, więc układ może przebiegać w dokumencie od lewej do prawej i z góry do dołu. Występują pewne wyjątki, na przykład tabele HTML mogą wymagać więcej niż 1 karnetu.

Układ współrzędnych jest ustalany względem ramki głównej. Użyte są współrzędne górnej i lewej krawędzi.

Układ to proces rekurencyjny. Rozpoczyna się od głównego mechanizmu renderowania, który odpowiada elementowi <html> dokumentu HTML. Układ jest kontynuowany rekurencyjnie przez część lub całość hierarchii ramek, obliczając dane geometryczne dla każdego mechanizmu renderowania, który tego wymaga.

Położenie głównego mechanizmu renderowania wynosi 0,0, a jego wymiary to widoczny obszar okna przeglądarki.

Wszystkie mechanizmy renderowania stosują metodę „układ” lub „reflow”. Każdy z nich wywołuje metodę układu elementów podrzędnych, które wymagają układu.

System brudnych bitów

Aby nie wprowadzać pełnego układu przy każdej niewielkiej zmianie, przeglądarki stosują system „brudnych bitów”. Mechanizm renderowania, który został zmieniony lub dodany jako „brudny” lub dodany do jego elementów podrzędnych, wymaga układu.

Istnieją 2 flagi: „brudne” i „dzieci są brudne”, co oznacza, że chociaż sam mechanizm renderowania może być poprawny, ma co najmniej 1 element podrzędny, który wymaga układu.

Układ globalny i przyrostowy

Układ można uruchomić na całym drzewie renderowania – jest to układ „globalny”. Możliwe przyczyny:

  1. Globalna zmiana stylu obejmująca wszystkie mechanizmy renderowania, np. zmiana rozmiaru czcionki.
  2. W wyniku zmiany rozmiaru ekranu

Układ może być przyrostowy. Układem można stosować tylko brudne mechanizmy renderowania (może to spowodować pewne uszkodzenia, które wymagają dodatkowych układów).

Układ przyrostowy jest wyzwalany (asynchronicznie), gdy mechanizmy renderowania są zabrudzone. Na przykład nowe mechanizmy renderowania są dołączane do drzewa renderowania po tym, jak dodatkowa treść pochodzi z sieci i została dodana do drzewa DOM.

Układ przyrostowy.
Rys. 18. Układ przyrostowy – układ tylko brudnych mechanizmów renderowania i ich elementów podrzędnych

Układ asynchroniczny i synchroniczny

Układ przyrostowy jest wykonywany asynchronicznie. Firefox umieszcza w kolejce „polecenia ponownego przepływu” w przypadku układów przyrostowych, a algorytm szeregowania uruchamia zbiorcze wykonanie tych poleceń. WebKit ma również licznik czasu, który wykonuje układ przyrostowy – drzewo jest przemierzane, a „brudne” mechanizmy renderowania zostają pozbawione.

Skrypty, które pytają o informacje o stylu, takie jak „offsetHeight”, mogą aktywować synchronicznie układ przyrostowy.

Układ globalny jest zwykle aktywowany synchronicznie.

Czasami układ jest wyzwalany jako wywołanie zwrotne po początkowym układzie, ponieważ niektóre atrybuty, takie jak pozycja przewijania, uległy zmianie.

Optymalizacje

Gdy układ jest wywoływany przez „zmianę rozmiaru” lub zmiany pozycji mechanizmu renderowania(a nie rozmiaru), rozmiary renderowania są pobierane z pamięci podręcznej i nie są ponownie obliczane...

W niektórych przypadkach modyfikowane jest tylko drzewo podrzędne, a układ nie rozpoczyna się od poziomu głównego. Może się tak zdarzyć w przypadkach, gdy zmiana ma charakter lokalny i nie ma wpływu na otoczenie, np. tekst wstawiony do pól tekstowych (w przeciwnym razie każde naciśnięcie klawisza powoduje uruchomienie układu rozpoczynającego się od poziomu głównego).

Proces tworzenia układu

Układ ma zwykle taki wzorzec:

  1. Nadrzędny mechanizm renderowania określa własną szerokość.
  2. Rodzic nadzoruje dzieci i:
    1. Umieść mechanizm renderowania podrzędnego (ustawia X i y).
    2. W razie potrzeby wywołuje układ potomny – jest zabrudzony, znajduje się w układzie globalnym lub z innego powodu – który oblicza wzrost dziecka.
  3. Element nadrzędny ustawia własną wysokość na podstawie skumulowanych wysokości elementów podrzędnych oraz wysokości marginesów i dopełnienia. Zostanie ona użyta przez element nadrzędny nadrzędnego mechanizmu renderowania.
  4. Ustawia wartość brudnego bitu na fałsz.

W przeglądarce Firefox jako parametr układu(nazywany „reflow”) jest używany obiekt „state” (nsHTMLReflowState). Stan obejmuje między innymi szerokość elementów nadrzędnych.

Wynikiem układu przeglądarki Firefox jest obiekt „metrics” (nsHTMLReflowMetrics). Będzie on zawierać obliczoną wysokość mechanizmu renderowania.

Obliczanie szerokości

Szerokość mechanizmu renderowania jest obliczana na podstawie szerokości bloku kontenera, właściwości „width” stylu mechanizmu renderowania, marginesów i obramowania.

np. szerokość tego elementu div:

<div style="width: 30%"/>

Wartość zostanie obliczona przez WebKit w następujący sposób(metoda klasy RenderBox calcWidth):

  • Szerokość kontenera to maksymalna liczba dostępnych kontenerów, która wynosi 0. W tym przypadku dostępna szerokość to contentWidth, która jest obliczana w ten sposób:
clientWidth() - paddingLeft() - paddingRight()

Parametr clientwidth i clientHeight reprezentuje wnętrze obiektu z wyłączeniem obramowania i paska przewijania.

  • Szerokość elementów to atrybut stylu „width”. Zostanie ona obliczona jako wartość bezwzględna przez obliczenie odsetka szerokości kontenera.

  • Dodano obramowanie poziome i dopełnienia.

Do tej pory była to obliczona „preferowana szerokość”. Teraz obliczona zostanie minimalna i maksymalna szerokość.

Jeśli preferowana szerokość jest większa niż maksymalna, stosowana jest maksymalna szerokość. Jeśli jest mniejsza niż minimalna szerokość (najmniejsza jednostka nierozdzielająca), stosowana jest minimalna szerokość.

Na wypadek, gdyby potrzebny był układ, wartości są przechowywane w pamięci podręcznej, ale szerokość nie zmienia się.

Podział wiersza

Gdy mechanizm renderowania w środku układu uzna, że musi zostać uszkodzony, zatrzyma się go i przekaże do jego elementu nadrzędnego, że musi zostać uszkodzony. Element nadrzędny tworzy dodatkowe mechanizmy renderowania i wywołuje na nich układ.

Obraz

Na etapie malowania drzewo renderowania jest analizowane, a metoda „paint()” mechanizmu renderowania zostaje wywołana w celu wyświetlenia treści na ekranie. Obraz wykorzystuje komponent infrastruktury UI.

Globalne i przyrostowe

Podobnie jak układ, obraz może mieć charakter globalny – całe drzewo – lub przyrostowy. W przypadku malowania przyrostowego niektóre mechanizmy renderowania zmieniają się w sposób, który nie ma wpływu na całe drzewo. Zmieniony mechanizm renderowania unieważnia prostokąt na ekranie. Spowoduje to, że system operacyjny uzna go za „brudny region” i wygeneruje zdarzenie „paint”. System operacyjny robi to mądrze i łączy kilka regionów w jeden. W Chrome jest to bardziej skomplikowane, ponieważ mechanizm renderowania wykonuje inny proces niż główny proces. Chrome symuluje zachowanie systemu operacyjnego do pewnego stopnia. Prezentacja nasłuchuje tych zdarzeń i przekazuje wiadomość do głównego katalogu renderowania. Drzewo będzie przemierzane aż do osiągnięcia odpowiedniego mechanizmu renderowania. Ponownie pomaluje się sam (i zwykle jego elementy podrzędne).

Zamówienie malarskie

CSS2 określa kolejność procesu malowania. W rzeczywistości w takiej kolejności elementy są ułożone w kontekstach warstwowych. Ta kolejność wpływa na malarstwo, ponieważ bloki są malowane od tyłu na wierzch. Kolejność grupowania mechanizmu renderowania bloków:

  1. background color
  2. obraz tła
  3. border
  4. dzieci
  5. konspekt

Lista wyświetlana w przeglądarce Firefox

Przeglądarka Firefox przechodzi nad drzewem renderowania i tworzy listę wyświetlania namalowanego prostokąta. Zawiera mechanizmy renderowania odpowiednie dla prostokąta we właściwej kolejności (tła mechanizmu renderowania, następnie ramki itp.).

W ten sposób drzewo wystarczy przemierzyć tylko raz, a nie kilka razy.

Przeglądarka Firefox optymalizuje ten proces, nie dodając do niej elementów, które będą ukryte, np. pod innymi nieprzezroczystymi elementami.

Prostokątne miejsce na dane WebKit

Przed ponownym malowaniem WebKit zapisuje stary prostokąt jako bitmapę. Następnie maluje tylko różnicę między nowymi i starymi prostokątami.

Zmiany dynamiczne

W odpowiedzi na zmianę przeglądarki starają się maksymalnie ograniczyć zakres działań. Dlatego zmiana koloru elementu spowoduje tylko ponowne wyrenderowanie elementu. Zmiana położenia elementu spowoduje zmianę układu i ponownego wyrenderowania elementu, jego elementów podrzędnych, a nawet elementów równorzędnych. Dodanie węzła DOM spowoduje jego ponowne wyrenderowanie i układ. Duże zmiany, takie jak zwiększenie rozmiaru czcionki w elemencie „html”, spowodują unieważnienie pamięci podręcznej, przekazanie i ponowne wyrenderowanie całego drzewa.

Wątki mechanizmu renderowania

Mechanizm renderowania jest jednowątkowy. Prawie wszystko, z wyjątkiem operacji sieciowych, odbywa się w jednym wątku. W przeglądarkach Firefox i Safari jest to główny wątek przeglądarki. W Chrome jest to główny wątek procesu karty.

Operacje sieciowe mogą być wykonywane w kilku równoległych wątkach. Liczba równoległych połączeń jest ograniczona (zwykle jest to od 2 do 6 połączeń).

Pętla zdarzeń

Wątek główny przeglądarki to pętla zdarzeń. To nieskończona pętla, która podtrzymuje ten proces. Czeka na zdarzenia (takie jak zdarzenia układu i renderowania), a potem je przetwarza. To jest kod przeglądarki Firefox głównej pętli zdarzeń:

while (!mExiting)
    NS_ProcessNextEvent(thread);

Model wizualny CSS2

Obszar roboczy

Zgodnie ze specyfikacją CSS2 termin canvas opisuje „obszar, w którym jest renderowana struktura formatowania”, czyli miejsce, w którym przeglądarka maluje treść.

Obszar roboczy jest nieskończony w przypadku każdego wymiaru przestrzeni, ale przeglądarki wybierają początkową szerokość na podstawie wymiarów widocznego obszaru.

Według strony www.w3.org/TR/CSS2/zindex.html obszar roboczy jest przezroczysty, jeśli znajduje się w innym obszarze i w razie potrzeby otrzymuje kolor zdefiniowany przez przeglądarkę.

Model pola CSS

Model pola CSS opisuje prostokątne pola, które są generowane dla elementów w drzewie dokumentów i rozmieszczone zgodnie z wizualnym modelem formatowania.

Każde pole zawiera obszar treści (np. tekst, obraz itp.) oraz opcjonalne otaczające go dopełnienie, obramowanie i marginesy.

Model pola CSS2
Rys. 19. Model pola CSS2

Każdy węzeł generuje 0... n takich pól.

Wszystkie elementy mają właściwość „display”, która określa typ wygenerowanego pola.

Przykłady:

block: generates a block box.
inline: generates one or more inline boxes.
none: no box is generated.

Domyślnie jest to wbudowane, ale arkusz stylów przeglądarki może określać inne wartości domyślne. Na przykład: domyślnym wyświetlaniem elementu „div” jest blok.

Przykład domyślnego arkusza stylów: www.w3.org/TR/CSS2/sample.html.

Schemat pozycjonowania

Są 3 schematy:

  1. Normalna: obiekt jest względem swojego miejsca w dokumencie. Oznacza to, że jej miejsce w drzewie renderowania jest takie samo jak miejsce w drzewie DOM i jest ustalane zgodnie z typem i wymiarami pola.
  2. Unoszenie: obiekt jest najpierw ułożony w normalny sposób, a następnie przesuwany tak daleko w lewo lub w prawo,
  3. Bezwzględna: obiekt jest umieszczany w drzewie renderowania w innym miejscu niż w drzewie DOM.

Schemat pozycjonowania jest określany przez właściwość „position” i atrybut „float”.

  • statyczne i względne powodują normalny przepływ
  • bezwzględna i stała przyczyna pozycjonowania

W przypadku pozycjonowania statycznego pozycja nie jest określona i używane jest pozycjonowanie domyślne. W innych schematach autor określa pozycję: góra, dół, lewo, prawo.

Sposób rozmieszczenia pola zależy od następujących czynników:

  • Typ pola
  • Wymiary pola
  • Schemat pozycjonowania
  • informacje zewnętrzne, takie jak rozmiar obrazu i rozmiar ekranu;

Typy pól

Ramka blokowa: tworzy bryłę – ma w oknie przeglądarki własny prostokąt.

Pole blokowe.
Rysunek 20. Pole blokowe

Ramka wbudowany: nie ma własnego bloku, ale znajduje się wewnątrz bloku zawierającego.

Pola wbudowane.
Rys. 21. Pola wbudowane

Bloki są formatowane w pionie jeden po drugim. Elementy wbudowane są sformatowane poziomo.

Blokowanie i formatowanie w tekście.
Rys. 22. Formatowanie blokowe i wbudowane

Pola wbudowane należy umieszczać w wierszach lub „polach liniowych”. Linie mają co najmniej taką wysokość, jak najwyższa ramka, ale mogą być wyższe, gdy pola są wyrównane „linia podstawowa”, czyli dolna część elementu jest wyrównana w punkcie kolejnego pola, innym niż dół. Jeśli szerokość kontenera jest niewystarczająca, elementy wbudowane zostaną umieszczone w kilku wierszach. Zazwyczaj dzieje się tak w akapicie.

Linie.
Rysunek 23. Linie

Pozycjonowanie

Krewny

Pozycjonowanie względne – pozycjonowane jak zwykle, a następnie przesuwane o wymaganą delta.

Pozycjonowanie względne.
Rys. 24. Pozycjonowanie względne

Swobodne

Pole zmiennoprzecinkowe jest przesunięte na prawo lub na lewo od linii. Ciekawe jest to, że wokół niej unoszą się inne prostokąty. Kod HTML:

<p>
  <img style="float: right" src="images/image.gif" width="100" height="100">
  Lorem ipsum dolor sit amet, consectetuer...
</p>

Będzie wyglądać tak:

Pływające.
Rysunek 25. Liczba zmiennoprzecinkowa

Bezwzględna i stała

Układ jest zdefiniowany dokładnie niezależnie od normalnego przepływu pracy. Element nie bierze udziału w normalnym przepływie. Wymiary są podawane względem kontenera. Na stałe kontener to widoczny obszar.

Stałe pozycjonowanie.
Ilustracja 26. Stałe pozycjonowanie

Reprezentacja warstwowa

Jest ona określana przez właściwość CSS z-index. Odzwierciedla trzeci wymiar pola: jego pozycję na osi „z”.

Pojemniki są podzielone na stosy (tzw. konteksty układania). W każdym stosie jako pierwsze namalowane są tylne elementy, a przednie elementy u góry, bliżej użytkownika. W przypadku nakładania się główny element ukryje poprzedni element.

Stosy są uporządkowane zgodnie z właściwością z-index. Pola z właściwością „z-index” tworzą stos lokalny. W widocznym obszarze znajduje się stos zewnętrzny.

Przykład:

<style type="text/css">
  div {
    position: absolute;
    left: 2in;
    top: 2in;
  }
</style>

<p>
  <div
    style="z-index: 3;background-color:red; width: 1in; height: 1in; ">
  </div>
  <div
    style="z-index: 1;background-color:green;width: 2in; height: 2in;">
  </div>
</p>

Wynik będzie następujący:

Stałe pozycjonowanie.
Rysunek 27. Stałe położenie

Chociaż czerwony element div poprzedza zielony element w znaczniku i byłby wcześniej wymalowany w zwykły sposób, właściwość z-index jest wyższa, więc jest bardziej wysunięta w stosie zawartym w obrębie pola głównego.

Zasoby

  1. Architektura przeglądarki

    1. Grosskurth, Alan. Architektura referencyjna dla przeglądarek internetowych (pdf)
    2. Gupta, Vineet. Jak działają przeglądarki – Część 1 – Architektura
  2. Analiza

    1. Aho, Sethi, Ullman, Kompilatory: zasady, techniki i narzędzia (znane też jako „Książka smoka”), Addison-Wesley, 1986 r.
    2. Ricka Jelliffe'a. Odważne i piękne: 2 nowe wersje robocze HTML5.
  3. Firefox

    1. L. David Baron, Szybsze HTML i CSS: wewnętrzne mechanizmy Layout Engine dla programistów stron internetowych.
    2. L. David Baron, Szybsze HTML i CSS: wewnętrzne mechanizmy Layout Engine dla programistów stron internetowych (film z prezentacji technicznej Google)
    3. L. David Baron, inżynier układów Mozilla
    4. L. David Baron, Mozilla Style System Dokumentacja
    5. Chris Waterson, Notes on HTML Reflow
    6. Chris Waterson, Gecko Review
    7. Alexander Larsson, The Life of an HTML HTTP request
  4. WebKit

    1. David Hyatt, Wdrażanie CSS(część 1)
    2. David Hyatt, An Review of WebCore
    3. David Hyatt, WebCore Rendering
    4. David Hyatt, The FOUC Problem
  5. Specyfikacje W3C

    1. Specyfikacja HTML 4.01
    2. Specyfikacja HTML5 W3C
    3. Specyfikacja kaskadowych arkuszy stylów (poziom 2, wersja 1) (CSS 2.1)
  6. Instrukcje tworzenia przeglądarek

    1. Firefox: https://developer.mozilla.org/Build_Documentation
    2. WebKit. http://webkit.org/building/build.html

Tłumaczenia

Ta strona została dwukrotnie przetłumaczona na japoński:

Możesz wyświetlić hostowane zewnętrznie tłumaczenia na język koreański i turecki.

Dziękujemy!