Kulisy nowoczesnych przeglądarek
Wstęp
Ten kompleksowy przewodnik dotyczący wewnętrznych operacji WebKit i Gecko jest wyników wielu badań izraelskich programistów Tali Garsiel. Ponad kilka przejrzała wszystkie opublikowane dane o działaniu przeglądarki i spędziła czytając kod źródłowy przeglądarki. Napisała:
Jako programista stron internetowych uczysz się obsługi przeglądarek pomaga w podejmowaniu lepszych decyzji i poznawaniu uzasadnień rozwoju sprawdzonych metod. Chociaż jest to dość długi dokument, poświęcaj czas na ich poszukiwanie. Warto to zrobić.
Paul Ireland, zespół ds. relacji z deweloperami Chrome
Wprowadzenie
Przeglądarki to najpopularniejsze oprogramowanie. W tym wprowadzeniu wyjaśnię,
które działają za kulisami. Zobaczymy, co się stanie, gdy wpiszesz google.com
w pasku adresu, aż na ekranie przeglądarki zobaczysz stronę Google.
Przeglądarki, o których będziemy mówić
Obecnie na komputerach używanych jest pięć głównych przeglądarek: Chrome, Internet Explorer, Firefox, Safari i Opera. Głównymi przeglądarkami na urządzeniach mobilnych są: Android, iPhone, Opera Mini i Opera Mobile, przeglądarka UC, Nokia S40/S60 oraz Chrome. Wszystkie z nich, z wyjątkiem przeglądarek Opera, są oparte na technologii WebKit. Podam przykłady z przeglądarek open source Firefoksa i Chrome oraz Safari (które jest częściowo na licencji open source). Według statystyk ze StatCounter (stan na czerwiec 2013 r.) Chrome, Firefox i Safari odpowiadają za około 71% użytkowników komputerów na całym świecie. Około 54% użytkowników korzysta z przeglądarki Androida, iPhone'a i Chrome.
Główna funkcjonalność przeglądarki
Główną funkcją przeglądarki jest prezentowanie wybranego zasobu internetowego przez zażądanie go od serwera i wyświetlenie go w oknie przeglądarki. Takim zasobem jest zwykle dokument HTML, ale może to być też plik PDF, obraz lub inny materiał. Lokalizacja zasobu jest określana przez użytkownika za pomocą identyfikatora URI (ang. Uniform Resource Identifier).
Sposób, w jaki przeglądarka interpretuje i wyświetla pliki HTML, jest określony w specyfikacjach HTML i CSS. Specyfikacje te określa organizacja W3C (World Wide Web Consortium), która zajmuje się standardami sieci. Przez lata przeglądarki spełniały tylko część specyfikacji i opracowały własne rozszerzenia. Spowodowało to poważne problemy ze zgodnością u autorów stron internetowych. Obecnie większość przeglądarek jest zgodna ze specyfikacjami.
Interfejsy użytkownika przeglądarki mają ze sobą wiele wspólnego. Do popularnych elementów interfejsu użytkownika należą:
- Pasek adresu do wstawiania identyfikatora URI
- Przyciski wstecz i dalej
- Opcje zakładek
- Przyciski odświeżania i zatrzymywania wczytywania bieżących dokumentów do odświeżania lub zatrzymywania
- Przycisk strony głównej, który prowadzi do strony głównej
Co dziwne, interfejs użytkownika przeglądarki nie został określony w żadnej formalnej specyfikacji. Po prostu korzysta on ze sprawdzonych metod ukształtowanych na przestrzeni lat i przez naśladowanie siebie przez przeglądarki. Specyfikacja HTML5 nie definiuje elementów interfejsu użytkownika, które musi mieć przeglądarka, a jedynie wymieniono kilka typowych elementów. Są to między innymi pasek adresu, stanu i narzędzi. Istnieją oczywiście pewne funkcje charakterystyczne dla określonej przeglądarki, takie jak menedżer pobierania w Firefoksie.
Infrastruktura wysokiego poziomu
Główne komponenty przeglądarki to:
- Interfejs użytkownika: zawiera pasek adresu, przycisk Wstecz/Dalej, menu zakładek itp. Zostaną wyświetlone wszystkie części przeglądarki z wyjątkiem okna, w którym wyświetla się żądana strona.
- Mechanizm przeglądarki: łączy działania interfejsu użytkownika z silnikiem renderowania.
- Mechanizm renderowania: odpowiedzialny za wyświetlanie żądanych treści. Jeśli na przykład żądana treść to HTML, mechanizm renderowania analizuje HTML i CSS oraz wyświetla przeanalizowaną treść na ekranie.
- Sieć: w przypadku wywołań sieciowych, takich jak żądania HTTP, stosowanie różnych implementacji dla różnych platform za pomocą interfejsu niezależnego od platformy.
- Interfejs użytkownika: służy do rysowania podstawowych widżetów, takich jak pola kombi i okna. Ten backend ujawnia ogólny interfejs, który nie jest specyficzny dla danej platformy. Znajdą się w nim metody interfejsu użytkownika systemu operacyjnego.
- Interpreter JavaScriptu. Służy do analizowania i wykonywania kodu JavaScript.
- Przechowywanie danych. To jest warstwa trwałości. Możliwe, że przeglądarka będzie musiała zapisać lokalnie różne dane, np. pliki cookie. Przeglądarki obsługują również mechanizmy przechowywania, takie jak localStorage, IndexedDB, WebSQL i FileSystem.
![Komponenty przeglądarki](https://web.dev/static/articles/howbrowserswork/image/browser-components-9cd8ff834cc9c.png?authuser=7&hl=pl)
Pamiętaj, że przeglądarki, takie jak Chrome, uruchamiają wiele wystąpień silnika renderowania – po 1 na każdą kartę. Każda karta działa w oddzielnym procesie.
Mechanizmy renderowania
Odpowiedzialność silnika renderowania to... Renderowanie, czyli wyświetlanie żądanej treści na ekranie przeglądarki.
Domyślnie mechanizm renderowania może wyświetlać dokumenty oraz obrazy HTML i XML. Może też wyświetlać inne typy danych za pomocą wtyczek lub rozszerzeń. np. wyświetlanie dokumentów PDF za pomocą 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 za pomocą języka CSS.
Różne przeglądarki wykorzystują różne silniki renderowania: Internet Explorer używa Trident, Firefox używa Gecko, Safari używa WebKit. Chrome i Opera (od wersji 15) używają Blink – rozwidlenia WebKit.
WebKit to silnik renderowania typu open source, który powstał jako silnik platformy Linux i został zmodyfikowany przez Apple, tak aby działał na komputerach Mac i w systemie Windows.
Główna procedura
Mechanizm renderowania zacznie pobierać zawartość żądanego dokumentu z warstwy sieciowej. Zwykle robi się to w porcjach po 8 KB.
Działanie mechanizmu renderowania wygląda tak:
![Podstawowy proces mechanizmu renderowania](https://web.dev/static/articles/howbrowserswork/image/rendering-engine-basic-fl-2fba02b24e871.png?authuser=7&hl=pl)
Mechanizm renderowania zacznie analizować dokument HTML i przekształcać elementy w węzły DOM w drzewie nazywanym „drzewem treści”. Wyszukiwarka analizuje dane o stylu – zarówno w zewnętrznych plikach CSS, jak i w elementach stylu. Informacje o stylu wraz z instrukcjami wizualnymi w kodzie HTML zostaną użyte do utworzenia innego drzewa: drzewa renderowania.
Drzewo renderowania zawiera prostokąty z atrybutami wizualnymi, takimi jak kolor i wymiary. Prostokąty muszą być ułożone w odpowiedniej kolejności.
Po utworzeniu drzewa renderowania przechodzi przez „układ”. proces tworzenia konta. Oznacza to, że trzeba podać dokładne współrzędne miejsca, w którym ma się on pojawić na ekranie. Następnym etapem jest malowanie – następuje przemierzenie drzewa renderowania, a każdy węzeł zostanie pomalowany za pomocą warstwy backendu interfejsu użytkownika.
Warto mieć świadomość, że jest to proces stopniowy. Dla wygody użytkowników mechanizm renderowania stara się jak najszybciej wyświetlić zawartość ekranu. Nie zaczeka, aż cały kod HTML zostanie przeanalizowany, zanim rozpocznie tworzenie i układ drzewa renderowania. Fragmenty treści zostaną przeanalizowane i wyświetlone, podczas gdy cały proces będzie kontynuowany, a pozostałe treści będą pobierane z sieci.
Przykłady głównego przepływu
![Główny proces WebKit.](https://web.dev/static/articles/howbrowserswork/image/webkit-main-flow-b779d50c0cf28.png?authuser=7&hl=pl)
![Główny proces mechanizmu renderowania geko w Mozilli.](https://web.dev/static/articles/howbrowserswork/image/mozillas-gecko-rendering-b18e445544965.jpg?authuser=7&hl=pl)
Na ilustracjach 3 i 4 widać, że chociaż rozwiązania WebKit i Gecko mają nieco inną terminologię, proces jest zasadniczo taki sam.
Gekon nazywa drzewo elementów sformatowanych „drzewem ramek”. Każdy element jest ramką. W silniku WebKit używany jest termin „Render Tree” (Drzewo renderowania). i składa się z elementu „Render Objects”. WebKit używa terminu „układ”. za rozmieszczanie elementów, a Gecko nazywa ją „Reflow”. „Załącznik” to termin WebKit określający łączenie węzłów DOM i informacje wizualne w celu utworzenia drzewa renderowania. Niewielka różnica niesemantyczna polega na tym, że gekon ma dodatkową warstwę między kodem HTML a drzewem DOM. Nazywa się to „ujściem treści” i fabryka do tworzenia elementów DOM. Omówimy każdy etap procesu:
Analizowanie danych – ogólne
Analiza to bardzo ważny proces w ramach silnika renderowania, dlatego omówimy go trochę bardziej szczegółowo. Zacznijmy od krótkiego wprowadzenia do analizy składni.
Analiza dokumentu oznacza przetłumaczenie go na strukturę, której może używać kod. Wynik analizy powstaje zwykle drzewo węzłów reprezentujące strukturę dokumentu. Nazywa się to drzewem analizy lub drzewem składni.
Na przykład analiza wyrażenia 2 + 3 - 1
może zwrócić to drzewo:
![Węzeł drzewa wyrażeń matematycznych.](https://web.dev/static/articles/howbrowserswork/image/mathematical-expression-t-6681a2511ead2.png?authuser=7&hl=pl)
Gramatyka
Analiza jest oparta na regułach składni, którą przestrzega dokument, czyli na języku lub formacie, w którym został napisany. Każdy format, który możesz przeanalizować, musi mieć deterministyczną gramatykę obejmującą słownictwo i reguły składni. Jest to tzw. gramatyka bez kontekstu. Języki ludzkie nie są takimi językami, dlatego nie można ich przeanalizować za pomocą konwencjonalnych technik analizy.
Kombinacja parsera i Lexera
Analiza można podzielić na 2 procesy podrzędne: analizę leksyczną i analizę składni.
Analiza leksykalna to proces dzielenia danych wejściowych na tokeny. Tokeny to słownik językowy, czyli zbiór prawidłowych elementów składowych. W języku ludzkim będzie ono zawierać wszystkie słowa, które występują w słowniku dla danego języka.
Analiza składni polega na stosowaniu reguł składni języka.
Parsery zazwyczaj dzielą efekty pracy między 2 komponenty: lexer (czasami nazywany tokenizerem), który odpowiada za podział danych wejściowych na prawidłowe tokeny, i parser, który odpowiada za skonstruowanie drzewa analizy przez analizę struktury dokumentu zgodnie z regułami składni języka.
Lekser wie, jak usuwać nieistotne znaki, takie jak odstępy i podziały wierszy.
![Od dokumentu źródłowego do analizy drzew](https://web.dev/static/articles/howbrowserswork/image/from-source-document-par-c9c8c59da1ef2.png?authuser=7&hl=pl)
Analiza ma charakter iteracyjny. Parser zwykle prosi o nowy token i próbuje dopasować token 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 wyświetlać prośby o tokeny, aż znajdzie regułę odpowiadającą wszystkim tym przechowywanym wewnętrznie tokenom. Jeśli nie zostanie znaleziona żadna reguła, parser zgłosi wyjątek. Oznacza to, że dokument jest nieprawidłowy i zawiera błędy składniowe.
Tłumaczenie
W wielu przypadkach drzewo analizy nie jest produktem końcowym. Przy tłumaczeniu często używa się analizy, która polega na przekształceniu dokumentu wejściowego na inny format. Przykładem może być kompilacja. Kompilator, który kompiluje kod źródłowy na kod maszynowy, najpierw analizuje go na drzewo, a następnie przekształca drzewo na dokument kodu maszynowego.
![Proces kompilacji](https://web.dev/static/articles/howbrowserswork/image/compilation-flow-57cfc3aa68a53.png?authuser=7&hl=pl)
Przykład analizy
Na rysunku 5 zbudowaliśmy drzewo analizy na podstawie wyrażenia matematycznego. Spróbujmy zdefiniować prosty język matematyczny i zobaczyć proces analizy.
Składnia:
- Elementy składowe składni języka to wyrażenia, hasła i operacje.
- W naszym języku możesz używać dowolnej liczby wyrażeń.
- Wyrażenie to „hasło” po nim następuje „operacja”; a następnie inne hasło
- Operacja to token plus lub minus
- Hasło to token będący liczbą całkowitą lub wyrażeniem
Przeanalizujmy dane wejściowe 2 + 3 - 1
.
Pierwszy podłańcuch, który pasuje do reguły, to 2
. Zgodnie z regułą 5 jest to hasło.
Drugie dopasowanie to 2 + 3
: pasuje do trzeciej reguły: hasła, po którym następuje operacja, po której następuje inne hasło.
Następne dopasowanie zostanie zastosowane tylko na końcu wpisanego tekstu.
2 + 3 - 1
jest wyrażeniem, ponieważ wiemy, że 2 + 3
jest hasłem, więc mamy hasło, po którym następuje operacja, po której następuje kolejne hasło.
Pole 2 + +
nie pasuje do żadnej reguły i dlatego jest nieprawidłowe.
Formalne definicje słownictwa i składni
Słownictwo jest zwykle wyrażane za pomocą wyrażeń regularnych.
Na przykład język jest zdefiniowany jako:
INTEGER: 0|[1-9][0-9]*
PLUS: +
MINUS: -
Jak widać, liczby całkowite są definiowane przez wyrażenie regularne.
Składnia jest zwykle definiowana w formacie BNF. Język jest zdefiniowany jako:
expression := term operation term
operation := PLUS | MINUS
term := INTEGER | expression
Mówiliśmy, że język może być analizowany przez zwykłe parsery, jeśli jego gramatyka nie zależy od kontekstu. Intuicyjna definicja gramatyki swobodnej kontekstowo to gramatyka, która da się w całości wyrazić w BNF. Formalną definicję znajdziesz Artykuł w Wikipedii na temat gramatyki bez kontekstu
Typy parserów
Istnieją 2 rodzaje parserów: parsery od dołu i od dołu. Intuicyjnym wyjaśnieniem jest to, że parsery odgórne analizują ogólną strukturę składni i próbują znaleźć dopasowanie do reguły. Parser oddolny zaczyna od danych wejściowych i stopniowo przekształca je w reguły składni, zaczynając od reguł niskiego poziomu aż do spełnienia reguł wysokiego poziomu.
Zobaczmy, jak te 2 rodzaje parserów przeanalizują nasz przykład.
Parser od góry rozpocznie się od reguły wyższego poziomu: rozpozna 2 + 3
jako wyrażenie. Następnie rozpozna 2 + 3 - 1
jako wyrażenie (proces identyfikowania wyrażenia ewoluuje i dopasowywane są do innych reguł, ale punkt początkowy jest regułą najwyższego poziomu).
Parser oddolny będzie skanować dane wejściowe do momentu dopasowania reguły. Funkcja ta zastąpi wtedy pasujące dane wejściowe regułą. Tak będzie do końca wprowadzania danych. Częściowo dopasowane wyrażenie jest umieszczane na stosie parsera.
Ten typ parsera od dołu jest nazywany parserem shift-reduce, ponieważ dane wejściowe są przesuwane w prawo (wyobraź sobie, że wskaźnik wskazuje początek na początku danych wejściowych, a następnie przesuwa się w prawo). Stopniowo ogranicza się do reguł składni.
Automatyczne generowanie parserów
Istnieją narzędzia, które umożliwiają wygenerowanie parsera. Przekaż mu informacje o gramatyce obowiązującej w Twoim języku – a także reguły dotyczące słownictwa i składni – a w ten sposób wygenerujesz działający parser. Tworzenie parsera wymaga dogłębnej wiedzy na temat analizy składni i nie jest łatwe do ręcznego utworzenia zoptymalizowanego parsera, więc generatory parserów mogą być bardzo przydatne.
WebKit korzysta z 2 dobrze znanych generatorów parserów: Flex do tworzenia leksera i Bison do tworzenia parsera (możesz natknąć się na nie z imionami Lex i Yacc). Dane wejściowe Flex to plik zawierający definicje tokenów regularnych. Dane wejściowe Bizona to reguły składni języka w formacie BNF.
Parser HTML
Zadaniem parsera HTML jest przetworzenie znaczników HTML w drzewo analizy.
Gramatyka HTML
Słownictwo i składnia języka HTML są zdefiniowane w specyfikacjach stworzonych przez organizację W3C.
Jak widać we wprowadzeniu do analizy składni, składnię gramatyki można zdefiniować formalnie za pomocą formatów takich jak BNF.
Niestety, wszystkie tematy konwencjonalnych parserów nie mają zastosowania w kodzie HTML (opracowaliśmy je nie dla zabawy – zostaną wykorzystane do analizy CSS i JavaScript). HTML nie może zostać łatwo zdefiniowany przez gramatykę bez kontekstu potrzebną przez parsery.
Istnieje formalny format definicji języka HTML – DTD (Document Type Definition), ale nie jest to gramatyka pozbawiona kontekstu.
Na pierwszy rzut oka wydaje się dziwne. Format HTML jest podobny do języka XML. Dostępnych jest wiele parserów XML. Istnieje odmiana języka HTML – XHTML w formacie XML – więc na czym polega różnica?
Różnica polega na tym, że metoda HTML jest bardziej „wybaczająca”: pozwala na pominięcie określonych tagów (które następnie są dodawane domyślnie), czasami pomijania tagów początkowych i końcowych itd. W ogóle jest to „delikatne” w porównaniu do sztywnej i wymagającej składni XML.
Ten pozornie drobny szczegół może zrobić coś miłego. Z jednej strony 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 sprawia, że pisanie słów gramatycznych jest trudne. Podsumowując, kod HTML nie może być łatwo analizowany przez konwencjonalne parsery, ponieważ jego gramatyka nie jest pozbawiona kontekstu. Kodu HTML nie można przeanalizować przez parser XML.
HTML DTD
Definicja HTML jest w formacie DTD. Ten format służy do definiowania języków z rodziny SGML. Format zawiera definicje wszystkich dozwolonych elementów, ich atrybutów i hierarchii. Jak widzieliśmy wcześniej, tag HTML DTD nie tworzy gramatyki pozbawionej kontekstu.
Dane DTD mają kilka odmian. Tryb ścisły jest zgodny wyłącznie ze specyfikacjami, ale inne tryby obsługują znaczniki używane przez przeglądarki w przeszłości. Zapewnia to zgodność wsteczną ze starszymi treściami. Aktualny rygorystyczny zapis DTD: www.w3.org/TR/html4/strict.dtd
DOM
Drzewo wyjściowe („drzewo analizy”) to drzewo elementów DOM i węzłów atrybutów. DOM to skrót od Document Object Model. Jest to prezentacja obiektów w dokumencie HTML i interfejs elementów HTML dla świata zewnętrznego, np. JavaScript.
Pierwiastek tego drzewa to „Dokument”. obiektu.
DOM jest w stosunku do znaczników niemal jeden do jednego. Na przykład:
<html>
<body>
<p>
Hello World
</p>
<div> <img src="example.png"/></div>
</body>
</html>
Znaczniki zostaną przeniesione do następującego drzewa DOM:
![Drzewo DOM przykładowych znaczników](https://web.dev/static/articles/howbrowserswork/image/dom-tree-the-example-mar-70be67fe14c9a.png?authuser=7&hl=pl)
Podobnie jak HTML, DOM jest określany przez organizację W3C. Więcej informacji znajdziesz na stronie www.w3.org/DOM/DOMTR. Jest to ogólna specyfikacja manipulowania dokumentami. W konkretnym module opisano elementy specyficzne dla języka HTML. Definicje kodu HTML znajdziesz tutaj: www.w3.org/TR/2003/REC-DOM-Level-2-HTML-20030109/idl-definitions.html.
Kiedy mówię, że drzewo zawiera węzły DOM, mam na myśli drzewo utworzone z elementów implementujących jeden z interfejsów DOM. Przeglądarki używają konkretnych implementacji, których inne atrybuty są używane wewnętrznie.
Algorytm analizy
Jak widzieliśmy w poprzednich sekcjach, kodu HTML nie można przeanalizować za pomocą zwykłych parserów, które działają od góry do dołu lub od dołu.
Przyczyny są następujące:
- Wybaczający charakter języka.
- Fakt, że przeglądarki mają tradycyjną tolerancję błędów, co pozwala na obsługę dobrze znanych przypadków nieprawidłowego kodu HTML.
- Analiza jest powtarzana. 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 funkcji
document.write()
) może dodawać dodatkowe tokeny, więc proces analizy modyfikuje dane wejściowe.
Przeglądarki, które nie mogą użyć zwykłych metod analizy, tworzą niestandardowe parsery do analizy kodu HTML.
Algorytm analizy został szczegółowo opisany w specyfikacji HTML5. Algorytm składa się z 2 etapów: tokenizacji i budowy drzewa.
Tokenizacja to analiza leksykalna, która polega na analizie danych wejściowych na tokeny. Wśród tokenów HTML są tagi początkowe, tagi końcowe, nazwy atrybutów i wartości atrybutów.
Tokenizer rozpoznaje token, przekazuje go konstruktorowi drzewa i zużywa kolejny znak w celu rozpoznania następnego tokena itd., aż do końca wartości wejściowej.
![Proces analizy HTML (pobrany ze specyfikacji HTML5)](https://web.dev/static/articles/howbrowserswork/image/html-parsing-flow-taken-6118c51b92b56.png?authuser=7&hl=pl)
Algorytm tokenizacji
Wynikiem działania algorytmu jest token HTML. Algorytm jest wyrażony jako maszyna stanowa. Każdy stan używa co najmniej jednego znaku ze strumienia wejściowego i zgodnie z nim aktualizuje kolejny stan. Na decyzję ma wpływ bieżący stan tokenizacji i stan budowy drzewa. Oznacza to, że ten sam użyty znak będzie zwracać różne wyniki dla prawidłowego następnego stanu w zależności od bieżącego stanu. Algorytm jest zbyt złożony, aby można go było w pełni opisać, dlatego przyjrzyjmy się prostemu przykładowi, który pomoże nam zrozumieć tę zasadę.
Podstawowy przykład tokenizacji poniższego kodu HTML:
<html>
<body>
Hello world
</body>
</html>
Stan początkowy to „Stan danych”.
Gdy zostanie znaleziony znak <
, stan zmienia się na „Stan otwarty tagu”.
Użycie znaku a-z
powoduje utworzenie „Tokenu tagu początkowego”, a stan zmienia się na „Stan nazwy tagu”.
Ten stan pozostaje niezmieniony, dopóki nie wykorzystasz znaku >
. Każdy znak zostanie dołączony do nowej nazwy tokena. W naszym przypadku utworzonym tokenem jest 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 działać w ten sam sposób.
Do tej pory wyemitowano tagi html
i body
. Wróciliśmy do „Stanu danych”.
Użycie znaku H
w zasadzie Hello world
spowoduje utworzenie i wydanie tokena postaci. Ten proces trwa aż do osiągnięcia wartości <
w zasadzie </body>
. Dla każdego znaku Hello world
wyślemy token postaci.
Wróciliśmy do „otwartego stanu tagu”.
Wykorzystanie następnego danych wejściowych /
spowoduje utworzenie end tag token
i przejście do „Stanu nazwy tagu”. Pozostaniemy w tym stanie do momentu osiągnięcia >
.Potem zostanie wyemitowany nowy token tagu i wrócimy do „Stanu danych”.
Pole </html>
będzie traktowane tak samo jak poprzednie.
![Tokenizacja przykładowych danych wejściowych](https://web.dev/static/articles/howbrowserswork/image/tokenizing-example-input-9d0cc36689681.png?authuser=7&hl=pl)
Algorytm budowy drzew
Podczas tworzenia parsera tworzony jest obiekt Document. Na etapie tworzenia drzewa zostanie zmodyfikowane drzewo DOM, którego korzenie zawiera dokument, a do niego zostaną dodane elementy. Każdy węzeł wyemitowany przez tokenizer zostanie przetworzony przez konstruktor drzewa. Specyfikacja określa, który element DOM jest z nim związany, i dla każdego tokena zostanie utworzona jego specyfikacja. Element zostanie dodany do drzewa DOM, a także do stosu otwartych elementów. Ten stos służy do korygowania niedopasowań zagnieżdżonych i niezamkniętych tagów. Algorytm jest też opisany jako maszyna stanowa. Stany te są nazywane „trybami wstawiania”.
Przyjrzyjmy się procesowi budowania drzewa dla przykładowych danych wejściowych:
<html>
<body>
Hello world
</body>
</html>
Dane wejściowe na etapie tworzenia drzewa to sekwencja tokenów z etapu tokenizacji. Pierwszy tryb to „tryb początkowy”. Odebranie kodu „html” spowoduje przejście do trybu „before 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". Treść . Element HTMLHeadElement zostanie utworzony domyślnie, mimo że nie mamy atrybutu „head”. token zostanie dodany do drzewa.
Teraz przejdziemy do trybu „w nagłówku”, a następnie do trybu „po głowie”. Token treści jest przetwarzany ponownie, obiekt HTMLBodyElement jest tworzony i wstawiony, a tryb jest przenoszony do "in body".
Tokeny postaci z „Hello world” są teraz odbierane. Pierwszy spowoduje utworzenie i wstawienie „Tekstu” i pozostałe znaki zostaną do niego dołączone.
Otrzymanie tokena końcowego treści spowoduje przeniesienie do trybu "after body". Otrzymujemy teraz tag końcowy HTML, który przeniesie nas do trybu "after after body". Odebranie tokena końca pliku zakończy analizowanie.
![Konstrukcja drzewa w przykładowym kodzie HTML.](https://web.dev/static/articles/howbrowserswork/image/tree-construction-exampl-4e9757a851f96.gif?authuser=7&hl=pl)
Działania po zakończeniu analizy
Na tym etapie przeglądarka oznaczy dokument jako interaktywny i rozpocznie analizę skryptów, które są „odroczone”. tryb: te, które powinny zostać wykonane po przeanalizowaniu dokumentu. Stan dokumentu zostanie zmieniony na „zakończony”. oraz „wczytywanie” .
Pełne algorytmy tokenizacji i konstrukcji drzewa znajdziesz w specyfikacji HTML5.
Przeglądarki tolerancja błędów
Nigdy nie zostanie wyświetlony komunikat o nieprawidłowej składni. na stronie HTML. Przeglądarka usuwa wszelkie nieprawidłowe treści i można kontynuować.
Weźmy na przykład ten kod HTML:
<html>
<mytag>
</mytag>
<div>
<p>
</div>
Really lousy HTML
</p>
</html>
Muszę złamać ponad milion reguł („mytag” to nie jest tag standardowy, złe zagnieżdżenie elementów „p” czy „div” itd.), a przeglądarka wyświetla to poprawnie i nie narzeka. Wiele kodu parsera naprawia więc błędy autora HTML.
Obsługa błędów jest dość spójna w przeglądarkach, ale o tyle nie jest częścią specyfikacji HTML. Podobnie jak zakładki i przyciski Wstecz/Dalej, to po prostu coś, co rozwijało się w przeglądarkach na przestrzeni lat. Niektóre nieprawidłowe konstrukcje HTML powtarzają się w wielu witrynach, a przeglądarki próbują naprawić je w sposób zgodny z innymi przeglądarkami.
Niektóre z tych wymagań definiuje specyfikacja HTML5. WebKit ładnie to podsumowuje w komentarzu na początku klasy parsera HTML.
Parser analizuje tokenizowane dane wejściowe do dokumentu, tworząc drzewo dokumentu. Jeśli dokument jest poprawnie sformułowany, można go łatwo przeanalizować.
Niestety musimy przetwarzać wiele dokumentów HTML, które nie są odpowiednio sformułowane, więc parser musi być tolerowany na błędy.
Musimy wyeliminować te problemy:
- Dodawany element jest wyraźnie zabroniony wewnątrz zewnętrznego tagu. W takim przypadku należy zamknąć wszystkie tagi do tego, który zabrania danego elementu, i dodać go później.
- Nie możemy dodawać elementu bezpośrednio. Możliwe, że osoba tworząca dokument zapomniała jakiś tag w środku (lub tag między nimi jest opcjonalny). Tak może być w przypadku następujących tagów: HTML HEAD BODY TBODY TR TD LI (czy coś mi umknęło?).
- Chcemy dodać element blokowy do elementu w tekście. Zamknij wszystkie wbudowane elementy do następnego elementu wyższego rzędu.
- Jeśli to nie pomoże, zamknij elementy, dopóki nie będziemy mogli ich dodać, lub zignoruj tag.
Spójrzmy na kilka przykładów tolerancji błędów w WebKit:
</br>
zamiast <br>
Niektóre strony używają </br>
zamiast <br>
. Aby zapewnić zgodność z IE i Firefox, WebKit traktuje ten kod 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 zostanie zaprezentowana użytkownikowi.
Zbędna tabela
Niedziałająca 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 2 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);
Usługa WebKit używa stosu dla bieżącej zawartości elementu: umieszcza tabelę wewnętrzną z zewnętrznego stosu tabel. Tabele będą teraz równorzędne.
Zagnieżdżone elementy formularza
Jeśli użytkownik umieści formularz w innym formularzu, drugi formularz zostanie zignorowany.
Kod:
if (!m_currentFormElement) {
m_currentFormElement = new HTMLFormElement(formTag, m_document);
}
Zbyt szczegółowa hierarchia tagów
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 końcowe HTML lub treści
I powtórzę – komentarz mówi sam za siebie.
if (t->tagName == htmlTag || t->tagName == bodyTag )
return;
Dlatego autorzy stron internetowych piszą poprawnie sformatowany kod HTML, chyba że chcą oni pojawiać się jako przykład we fragmencie kodu tolerancji błędów w WebKit.
Analiza CSS
Pamiętasz omawiane we wstępie pojęcia związane z analizą? Cóż, w przeciwieństwie do języka HTML, CSS to gramatyka uzależniona od kontekstu i można ją analizować za pomocą typów parserów opisanych we wprowadzeniu. W rzeczywistości specyfikacja CSS definiuje gramatykę leksyczną i składniową CSS.
Oto kilka przykładów:
Gramatyka leksykalna (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" jest skrótem od identyfikatora, np. nazwy klasy. „name” jest identyfikatorem elementu (odwołanym przez „#” )
Gramatyka składni została 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*] ')' ]
;
Objaś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 formalnie 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 oznacza odstęp). Zestaw reguł zawiera nawiasy klamrowe, a w nich deklaracja lub opcjonalnie liczbę deklaracji rozdzielonych średnikami. „deklaracja” i „selektor” zostaną zdefiniowane w poniższych definicjach BNF.
Parser CSS WebKit
WebKit korzysta z generatorów parserów Flex i Bison do automatycznego tworzenia parserów na podstawie plików gramatyki CSS. Jak pamiętasz ze wprowadzenia do parsera, Bison tworzy parser od dołu do góry, shift-reduce. W przeglądarce Firefox używany jest parser od góry zapisany ręcznie. W obu przypadkach każdy plik CSS jest analizowany na obiekt StyleSheet. Każdy obiekt zawiera reguły CSS. Obiekty reguł CSS zawierają obiekty selektora i deklaracji oraz inne obiekty odpowiadające gramatyce CSS.
![Analizuję kod CSS.](https://web.dev/static/articles/howbrowserswork/image/parsing-css-4531ebee58764.png?authuser=7&hl=pl)
Kolejność przetwarzania skryptów i arkuszy stylów
Skrypty
Model sieci jest synchroniczny. Autorzy oczekują, że skrypty będą analizowane i uruchamiane natychmiast, gdy parser dotrze do tagu <script>
.
Analiza dokumentu zostanie zatrzymana do czasu wykonania skryptu.
Jeśli skrypt jest zewnętrzny, zasób musi zostać najpierw pobrany z sieci – proces ten odbywa się także synchronicznie i powoduje zatrzymanie analizy do momentu pobrania zasobu.
Był to model przez wiele lat, a dodatkowo jest określony w specyfikacjach HTML4 i 5.
Autorzy mogą dodać atrybut „odrocz” do skryptu, co nie zatrzyma analizy dokumentu i zostanie wykonane po jego przeanalizowaniu. HTML5 dodaje opcję oznaczania skryptu jako asynchronicznego, aby był analizowany i wykonywany przez inny wątek.
Analiza spekulacyjna
Tę optymalizację przeprowadzają zarówno WebKit, jak i Firefox. Podczas wykonywania skryptów inny wątek analizuje pozostałą część dokumentu, sprawdzając, jakie inne zasoby muszą zostać załadowane z sieci, i wczytuje je. Dzięki temu zasoby mogą być ładowane przy połączeniach równoległych i zwiększać ogólną szybkość działania. Uwaga: parser spekulacyjny analizuje tylko odwołania do zasobów zewnętrznych, takich jak zewnętrzne skrypty, arkusze stylów i obrazy – nie modyfikuje drzewa DOM, które trafia do głównego parsera.
Arkusze stylów
Arkusze stylów mają natomiast inny model. Zasadniczo wygląda na to, że skoro arkusze stylów nie zmieniają drzewa DOM, nie ma powodu, aby na nie czekać i zatrzymywać analizę dokumentu. Istnieje jednak problem ze skryptami, które na etapie analizy dokumentu proszą o informacje o stylu. Jeśli styl nie zostanie wczytany i przeanalizowany, skrypt otrzyma błędne odpowiedzi, co najwyraźniej powoduje wiele problemów. To wygląda na skrajny przypadek, ale jest dość powszechny. Firefox blokuje wszystkie skrypty, gdy arkusz stylów jest nadal wczytywany i analizowany. WebKit blokuje skrypty tylko wtedy, gdy próbują uzyskać dostęp do określonych właściwości stylów, na które mogą mieć wpływ niewczytane arkusze stylów.
Renderowanie budowy drzewa
W trakcie 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 to wizualna reprezentacja dokumentu. To drzewo umożliwia malowanie treści we właściwej kolejności.
W przeglądarce Firefox elementy w drzewie renderowania nazywa się „ramkami”. WebKit używa terminu mechanizm renderowania lub obiektu renderowania.
Mechanizm renderowania wie, jak ułożyć oraz malować siebie i swoje dzieci.
Klasa RenderObject w WebKit, klasa podstawowa mechanizmów renderowania, ma następującą 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 reprezentuje prostokątny obszar odpowiadający polu CSS węzła, zgodnie ze specyfikacją CSS2. Zawiera informacje geometryczne, takie jak szerokość, wysokość i pozycja.
Na typ pola ma wpływ pole „display” wartości atrybutu stylu odpowiedniego dla węzła (patrz sekcja obliczanie stylu). Oto kod WebKit, który określa, jaki mechanizm renderowania należy utworzyć dla węzła DOM:
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;
}
Uwzględniany jest też typ elementu, na przykład elementy sterujące formularza i tabele zawierają specjalne ramki.
Jeśli element chce utworzyć specjalny mechanizm renderowania, w WebKit zastępuje metodę createRenderer()
.
Mechanizmy renderowania wskazują obiekty stylu zawierające informacje niegeometryczne.
Relacja drzewa renderowania do drzewa DOM
Mechanizmy renderowania odpowiadają elementom DOM, ale relacja nie prowadzi do jednego. Niewizualne elementy DOM nie są wstawiane do drzewa renderowania. Na przykład: „head” . Również elementy, których wyświetlana wartość została przypisana do „none” (brak) nie są widoczne w drzewie (podczas gdy elementy z widocznością „ukryte” pojawią się w drzewie).
Istnieją elementy DOM, które odpowiadają kilku obiektom wizualnym. Zazwyczaj są to elementy o złożonej strukturze, których nie da się opisać za pomocą pojedynczego prostokąta. Na przykład przycisk „Wybierz” ma trzy mechanizmy renderowania: jeden dla obszaru wyświetlania, jeden dla pola listy i jeden dla przycisku. Również jeśli tekst jest podzielony na kilka wierszy, ponieważ szerokość jest niewystarczająca dla jednego wiersza, 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 musi zawierać tylko elementy blokowe albo tylko elementy wbudowane. W przypadku treści mieszanej utworzone zostaną anonimowe mechanizmy renderowania blokowe, które opakują wbudowane elementy.
Niektóre obiekty renderowania odpowiadają węzłowi DOM, ale nie w tym samym miejscu w drzewie. Elementy pływające oraz bezwzględnie umieszczone w wycieku są poza granicami, są umieszczane w innej części drzewa i zmapowane na prawdziwą ramkę. Ramka zastępcza to miejsce, w którym powinny się znajdować.
![Drzewo renderowania i odpowiadające mu drzewo DOM.](https://web.dev/static/articles/howbrowserswork/image/the-render-tree-the-corr-f699894ef4c75.png?authuser=7&hl=pl)
Procedura budowania drzewa
W przeglądarce Firefox prezentacja jest zarejestrowana jako detektor aktualizacji DOM.
Prezentacja przekazuje tworzenie klatek do funkcji FrameConstructor
, a konstruktor określa styl (patrz Obliczanie stylu) i tworzy ramkę.
W WebKit proces rozpoznawania stylu i tworzenia mechanizmu renderowania nosi nazwę „załącznik”. Każdy węzeł DOM ma element „dołącz” . Przyłącze jest synchroniczne, wstawienie węzła do drzewa DOM wywołuje nowy węzeł „dołącz” .
Przetwarzanie tagów html i body skutkuje stworzeniem korzenia drzewa renderowania.
Główny obiekt renderowania odpowiada temu, co specyfikacja CSS wywołuje zawierający go blok: najwyższy blok, który zawiera wszystkie pozostałe bloki. Jego wymiary to widoczny obszar, czyli wymiary obszaru wyświetlania okna przeglądarki.
W przeglądarce Firefox nazwa ta to ViewPortFrame
, a w WebKit jest to RenderView
.
Jest to obiekt renderowania, na który wskazuje dokument.
Reszta drzewa tworzy się w postaci wstawiania węzłów DOM.
Zobacz specyfikację modelu przetwarzania dla CSS2.
Obliczenia stylu
Tworzenie drzewa renderowania wymaga obliczenia właściwości wizualnych każdego renderowanego obiektu. W tym celu obliczamy właściwości stylu każdego elementu.
Styl zawiera arkusze stylów o różnym źródle, wbudowane elementy stylów i właściwości wizualne w kodzie HTML (np. właściwość „bgcolor”). Później zostaje przekonwertowany na pasujące właściwości stylów CSS.
Źródła arkuszy stylów to domyślne arkusze stylów przeglądarki, arkusze stylów dostarczane przez autora strony i arkusze stylów użytkownika – są to arkusze dostarczane przez użytkownika przeglądarki (przeglądarki umożliwiają definiowanie ulubionych stylów. W przeglądarce Firefox można na przykład umieścić arkusz stylów w sekcji „Profil tej przeglądarki”. folder).
Obliczanie stylu wiąże się z kilkoma trudnościami:
- Dane stylu to bardzo duża konstrukcja z wieloma właściwościami stylu, co może powodować problemy z pamięcią.
Znalezienie reguł dopasowania dla każdego elementu może powodować problemy z wydajnością, jeśli nie jest on zoptymalizowany. Przemierzanie całej listy reguł dla każdego elementu w celu znalezienia dopasowań to ciężkie zadanie. Selektory mogą mieć złożoną strukturę, przez co proces dopasowywania rozpoczyna się na pozornie obiecującej ścieżce, która okazuje się bezcelowa, i konieczne jest wypróbowanie innej ścieżki.
Na przykład ten selektor złożony:
div div div div{ ... }
Oznacza, że reguły dotyczą 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>
. Na drzewie wybierasz pewną ścieżkę do sprawdzenia. Konieczne może być przejrzenie drzewa węzłów w górę, by przekonać się, że są tylko dwa elementy div, a reguła nie ma zastosowania. Następnie musisz wypróbować inne ścieżki w drzewie.Stosowanie tych reguł wymaga stosunkowo złożonych reguł kaskadowych, które definiują hierarchię reguł.
Zobaczmy, jak przeglądarki radzą sobie z tymi problemami:
Udostępnianie danych o stylu
Węzły WebKit odwołują się do obiektów stylu (RenderStyle). W pewnych warunkach obiekty te mogą być współdzielone przez węzły. Węzły te należą do rodzeństwa lub kuzyna oraz:
- Elementy muszą być w tym samym stanie myszy (np.jeden nie może być w :hover, a drugi nie).
- Żaden z elementów nie powinien mieć identyfikatora
- Nazwy tagów powinny być takie same
- Atrybuty klas powinny być takie same
- Zbiór zmapowanych atrybutów musi być taki sam
- Stany połączenia muszą być takie same
- Stany skupienia muszą być takie same
- Selektory atrybutów nie powinny mieć wpływu na żaden z elementów, gdzie dopasowanie to definicja dowolnego selektora atrybutów, który korzysta z selektora atrybutu w dowolnej pozycji w selektorze.
- Elementy nie mogą zawierać atrybutu stylu wbudowanego
- Nie mogą w ogóle być używane selektory elementów równorzędnych. WebCore po prostu uruchamia przełącznik globalny po napotkaniu dowolnego selektora równorzędnego i wyłącza udostępnianie stylów dla całego dokumentu, gdy jest dostępny. Obejmuje to selektor + i selektory takie jak :first-child i :last-child.
Drzewo reguł przeglądarki Firefox
W przeglądarce Firefox są 2 dodatkowe drzewa, które ułatwiają obliczanie stylu: drzewo reguł i drzewo kontekstu stylu. WebKit zawiera również obiekty stylu, ale nie są one przechowywane w drzewie, takim jak drzewo kontekstu stylu, ale tylko węzeł DOM wskazuje odpowiedni styl.
![Drzewo kontekstu stylu w przeglądarce Firefox.](https://web.dev/static/articles/howbrowserswork/image/firefox-style-context-tre-f578b75b74df7.png?authuser=7&hl=pl)
Konteksty stylu zawierają wartości końcowe. Wartości są obliczane przez zastosowanie wszystkich pasujących reguł we właściwej kolejności i wykonywanie manipulacji, które przekształcają je z wartości logicznych w konkretne. Jeśli na przykład wartość logiczna stanowi odsetek powierzchni ekranu, zostanie ona obliczona i przekształcona w jednostki bezwzględne. Pomysł na drzewo reguł jest bardzo mądry. Umożliwia współdzielenie tych wartości między węzłami, co pozwala uniknąć ich ponownego przetwarzania. 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 ścieżki do znalezionych reguł dopasowania reguł. Reguły są przechowywane 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, aby zobaczyć ścieżki w postaci drzewa jako słowa w leksykonie. Załóżmy, że mamy już obliczone drzewo reguł:
![Obliczone drzewo reguł](https://web.dev/static/articles/howbrowserswork/image/computed-rule-tree-f874f412bbaf.png?authuser=7&hl=pl)
Załóżmy, że musimy dopasować reguły do innego elementu w drzewie treści i okazuje się, że dopasowane reguły (we właściwej kolejności) to reguły B-E-I. Mamy już tę ścieżkę w drzewie, ponieważ obliczyliśmy ścieżkę A-B-E-I-L. Będziemy mieli mniej do zrobienia.
Zobaczmy, jak drzewo uratuje nam pracę.
Dzielenie na elementy struct
Konteksty stylu są podzielone na elementy „struct”. Te elementy typu struct zawierają informacje o stylu dla określonej kategorii, takiej jak obramowanie lub kolor. Wszystkie właściwości w określonej strukturze są dziedziczone lub niedziedziczone. Właściwości dziedziczone to właściwości, które są dziedziczone z elementu nadrzędnego, chyba że zostały zdefiniowane przez element. Niedziedziczone właściwości (nazywane właściwościami „resetującymi”) używają wartości domyślnych, jeśli nie są zdefiniowane.
Drzewo to pomaga nam, buforując całe obiekty struct (zawierające obliczone wartości końcowe) w drzewie. Chodzi o to, że jeśli dolny węzeł nie dostarczył definicji elementu struct, można użyć struktury z pamięci podręcznej w górnym węźle.
Obliczanie kontekstu stylu przy użyciu drzewa reguł
Przy obliczaniu kontekstu stylu dla określonego elementu najpierw obliczamy ścieżkę w drzewie reguł lub używamy istniejącej ścieżki. Następnie zaczynamy stosować reguły w tej ścieżce, aby wypełnić elementy struct w nowym kontekście stylu. Zaczynamy od dolnego węzła ścieżki – tego, który ma najwyższy priorytet (zwykle jest to najbardziej szczegółowy selektor), i przechodzimy przez drzewo aż do wypełnienia całej struktury. Jeśli nie ma specyfikacji elementu struct w tym węźle reguły, możemy znacznie przeprowadzić optymalizację – idziemy do drzewa, aż znajdziemy węzeł, który w pełni go określa i wskazuje go – to najlepsza optymalizacja – cała struktura jest współdzielona. Pozwala to zaoszczędzić obliczenie wartości końcowych i pamięć.
Jeśli znajdziemy częściowe definicje, idziemy do góry, aż element struct zostanie wypełniony.
Jeśli nie znajdziemy żadnych definicji elementu struct, to w przypadku, gdy element struct jest „dziedziczony”. type, wskazujemy strukturę elementu nadrzędnego w drzewie kontekstowym. W tym przypadku udało się nam też udostępnić elementy struct. Jeśli jest to struktura resetowania, zostaną użyte wartości domyślne.
Jeśli najbardziej szczegółowy węzeł dodaje wartości, musimy przeprowadzić dodatkowe obliczenia, aby przekształcić go w wartości rzeczywiste. Wynik ten zapisujemy w pamięci podręcznej w węźle drzewa, aby mógł być używany przez elementy podrzędne.
W przypadku elementów, które mają podobne elementy lub brata wskazujące ten sam węzeł drzewa, można im współdzielić kontekst stylu.
Oto przykład: Załóżmy, że mamy ten 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 uprościć ten proces, załóżmy, że musisz wypełnić tylko dwa elementy strukturalne: strukturę koloru i strukturę marginesu. Struktura kolorów zawiera tylko jeden element: kolor Struktura marginesów zawiera cztery boki.
Powstałe drzewo reguł będzie wyglądać tak (węzły są oznaczone nazwą węzła: numer reguły, do której one prowadzą):
![Drzewo reguł](https://web.dev/static/articles/howbrowserswork/image/the-rule-tree-23f05b0dac33f.png?authuser=7&hl=pl)
Drzewo kontekstu będzie wyglądać tak (nazwa węzła: węzeł reguły, do którego wskazują):
![Drzewo kontekstu](https://web.dev/static/articles/howbrowserswork/image/the-context-tree-771124b7cb80d.png?authuser=7&hl=pl)
Załóżmy, że po analizie kodu HTML dochodzimy do drugiego tagu <div>
. Musimy utworzyć kontekst stylu dla tego węzła i wypełnić jego struktury stylów.
Dopasujemy reguły i ustalimy, że reguły dopasowania w przypadku elementu <div>
to 1, 2 i 6.
Oznacza to, że w drzewie istnieje już ścieżka, której może użyć nasz element, i musimy 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ć elementy struktur stylu. Zaczniemy od wypełnienia struktury marży. Ponieważ ostatni węzeł reguły (F) nie jest dodawany do struktury marginesu, możemy przejść w górę drzewa, dopóki nie znajdziemy struktury z pamięci podręcznej obliczonej podczas poprzedniego wstawiania węzła i jej nie użyjemy. Znajdujemy go w węźle B, który jest najwyższym węzłem z określonymi regułami marginesów.
Struktura kolorów ma swoją definicję, dlatego nie możemy użyć tej z pamięci podręcznej. Kolor ma jeden atrybut, więc nie trzeba ingerować w drzewo, aby wypełnić inne atrybuty. Obliczymy wartość końcową (przekonwertuj ciąg na RGB itp.) i zapiszemy obliczoną strukturę w pamięci podręcznej w tym węźle.
Praca nad drugim elementem <span>
jest jeszcze łatwiejsza. Dopasujemy reguły i dowiemy się, że odnoszą się one do reguły G, takiej jak poprzedni zakres.
Ponieważ mamy elementy równorzędne, które wskazują ten sam węzeł, możemy udostępnić cały kontekst stylu i wskazać kontekst poprzedniego rozpiętości.
W przypadku elementów struct zawierających reguły dziedziczone z elementu nadrzędnego buforowanie odbywa się w drzewie kontekstu (właściwość koloru jest w rzeczywistości dziedziczona, ale Firefox traktuje ją jako zresetowaną i zapisuje ją w pamięci podręcznej drzewa reguł).
Jeśli na przykład dodaliśmy reguły dotyczące czcionek w akapicie:
p {font-family: Verdana; font size: 10px; font-weight: bold}
Wtedy element akapitu, który jest elementem podrzędnym elementu div w drzewie kontekstowym, mógł mieć taką samą strukturę czcionki jak element nadrzędny. Oznacza to, że nie określono żadnych reguł dotyczących czcionek dla akapitu.
W pakiecie WebKit, który nie ma drzewa reguł, pasujące deklaracje są sprawdzane cztery razy. Stosowane są pierwsze mniej ważne właściwości o wysokim priorytecie (czyli właściwości, które należy zastosować najpierw, ponieważ są od nich zależne, np. reklamy displayowe), potem reguły o wysokim priorytecie, ważne, potem o normalnym priorytecie, bez znaczenia, a następnie reguły o normalnym priorytecie. Oznacza to, że właściwości, które pojawiają się wiele razy, są zamykane zgodnie z prawidłową kolejnością kaskadową. Wygrywa ostatnie.
Podsumowując: udostępnienie obiektów stylu (całkowicie lub niektórych zawartych w nich elementów struct) rozwiązuje problemy 1 i 3. Drzewo reguł Firefoksa pomaga też stosować właściwości we właściwej kolejności.
Manipulowanie regułami, aby ułatwić dopasowanie
Istnieje kilka źródeł reguł stylu:
- Reguły CSS w zewnętrznych arkuszach stylów lub w elementach stylu.
css p {color: blue}
- Atrybuty stylu wbudowanego, takie jak
html <p style="color: blue" />
- Atrybuty wizualne HTML (zmapowane na odpowiednie reguły stylu)
html <p bgcolor="blue" />
Ostatnie dwa można łatwo dopasować do elementu, ponieważ to on jest właścicielem atrybutów stylu, a atrybuty HTML można zmapować, używając elementu jako klucza.
Jak wspomnieliśmy wcześniej w punkcie 2, dopasowywanie reguł CSS może być trudniejsze. Aby rozwiązać ten problem, wprowadziliśmy manipulowanie regułami w celu ułatwienia dostępu.
Po przeanalizowaniu arkusza stylów reguły są dodawane do jednej z kilku map haszujących, zgodnie z selektorem. Możesz wybrać: identyfikator, nazwę klasy, nazwę tagu oraz ogólną mapę, która nie pasuje do tych kategorii. Jeśli selektorem jest identyfikator, reguła zostanie dodana do mapy identyfikatorów. Jeśli jest to klasa, zostanie dodana do mapy klas itd.
Taka manipulacja znacznie ułatwia dopasowywanie reguł. Nie musisz sprawdzać każdej deklaracji: możemy wyodrębnić z map odpowiednie reguły dla elementu. Ta optymalizacja eliminuje ponad 95% reguł, więc nie trzeba ich nawet brać pod uwagę podczas procesu dopasowywania(4.1).
Zobaczmy na przykład następujące reguły stylu:
p.error {color: red}
#messageDiv {height: 50px}
div {margin: 5px}
Pierwsza reguła zostanie wstawiona do mapy zajęć. Druga w mapie identyfikatorów, a trzecia – w mapie tagów.
Dla tego 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 zajęć będzie zawierać błąd klucz, zgodnie z którym reguła dla „p.error” zostanie znaleziony. Element div będzie mieć odpowiednie reguły w mapie identyfikatorów (klucz to identyfikator) i w mapie tagów. Pozostaje więc już tylko ustalić, które reguły wyodrębnione przez klucze naprawdę pasują.
Jeśli na przykład reguła dla elementu div to:
table div {margin: 5px}
W dalszym ciągu zostanie on wyodrębniony z mapy tagów, ponieważ klucz znajduje się skrajnie z prawej strony, ale nie pasuje do elementu div, który nie ma elementu nadrzędnego tabeli.
Manipulacja ta jest wykonywana zarówno w przypadku 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 właściwość nie jest zdefiniowana przez żadną pasującą regułę, niektóre właściwości mogą być dziedziczone przez obiekt stylu elementu nadrzędnego. Pozostałe właściwości mają wartości domyślne.
Problem zaczyna się, gdy istnieje więcej niż jedna definicja – tu następuje kaskadowa kolejność rozwiązań problemu.
Deklaracja właściwości stylu może znajdować się w wielu arkuszach stylów, a także wielokrotnie w różnych arkuszach. Oznacza to, że kolejność stosowania reguł jest bardzo ważna. Jest to tzw. „kaskada”, zamówienie. Zgodnie ze specyfikacją CSS2 kolejność kaskadowa jest (od najniższej do najwyższej):
- Deklaracje przeglądarki
- Deklaracje normalnego użytkownika
- Normalne deklaracje autora
- Utwórz ważne deklaracje
- Deklaracje ważnych użytkowników
Deklaracje przeglądarki są najmniej ważne, a użytkownik zastępuje autora tylko wtedy, gdy została oznaczona jako ważna. Deklaracje z tą samą kolejnością będą sortowane według specyficzności, a następnie w kolejności, w jakiej zostały określone. Atrybuty wizualne HTML są przekształcane na deklaracje CSS . Są one traktowane jako reguły dotyczące autora o niskim priorytecie.
Zgodność ze specyfikacją
Szczegółowość selektora jest określona w specyfikacji CSS2 w ten sposób:
- liczba 1, jeśli deklaracja, z której pochodzi, to „style” atrybut zamiast reguły z selektorem, w przeciwnym razie 0 (= a)
- Policz liczbę atrybutów identyfikatora w selektorze (= b)
- zliczać inne atrybuty i pseudoklasy w selektorze (= c)
- zliczać nazwy elementów i pseudoelementów w selektorze (= d)
Połączenie 4 cyfr a-b-c-d (w systemie liczbowym o dużej podstawie) pozwala uzyskać precyzję.
Wymagana liczba jest zdefiniowana na podstawie największej liczby w jednej z kategorii.
Na przykład przy a=14 możesz użyć liczby szesnastkowej. W mało prawdopodobnym przypadku, gdy a=17, będziesz potrzebować 17-cyfrowego podstawy. Może się to zdarzyć w przypadku użycia takiego selektora: html treść 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ły są sortowane zgodnie z regułami kaskadowymi.
WebKit korzysta z sortowania bąbelkowego w przypadku małych list i scalania w przypadku dużych.
WebKit wykorzystuje sortowanie, zastępując operator >
w regułach:
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, która wskazuje, czy zostały wczytane wszystkie arkusze stylów najwyższego poziomu (w tym @imports). Jeśli styl nie zostanie w pełni załadowany podczas dołączania, używane są uchwyty miejsc i są one oznaczone w dokumencie. Zostaną one obliczone ponownie po wczytaniu arkuszy stylów.
Układ
Po utworzeniu i dodaniu do drzewa mechanizmu renderowania nie ma on położenia ani rozmiaru. Obliczanie tych wartości jest nazywane układem lub przeformatowaniem.
HTML korzysta z modelu układu opartego na przepływie, co oznacza, że w większości przypadków możliwe jest obliczanie geometrii w ramach jednego przebiegu. Późniejsze elementy „w toku” zwykle nie mają wpływu na geometrię elementów, które pojawiają się wcześniej „w toku”, więc układ może przechodzić od lewej do prawej, z góry do dołu. Istnieją wyjątki, na przykład tabele HTML mogą wymagać więcej niż jednej karty.
Układ współrzędnych jest położony względem ramki głównej. W użyciu są używane współrzędne lewej i górnej części ekranu.
Układ jest procesem rekurencyjnym. Rozpoczyna się na głównym mechanizmie renderowania, który odpowiada elementowi <html>
dokumentu HTML. Układ rekurencyjnie przechodzi przez część lub całą hierarchię ramek, obliczając informacje geometryczne dla każdego mechanizmu renderowania, który go wymaga.
Pozycja głównego mechanizmu renderowania to 0,0, a jego wymiary to widoczny obszar, czyli widoczną część okna przeglądarki.
Wszystkie mechanizmy renderowania mają „układ” lub „przeformatować” , każdy mechanizm renderowania wywołuje metodę „Layout” swoich elementów podrzędnych, które wymagają szablonu.
Zabrudzony system bitów
Aby nie wprowadzać pełnego układu przy każdej drobnej zmianie, przeglądarki używają „nieczystego fragmentu” systemu. Zmieniony lub dodany mechanizm renderowania oznacza siebie i jego elementy podrzędne jako „brudny”: wymaga układu.
Są 2 flagi: „Dzieci są brudne” i „Dzieci są brudne” co oznacza, że chociaż mechanizm renderowania może być prawidłowy, ma co najmniej jeden element podrzędny, który wymaga układu.
Układ globalny i przyrostowy
Układ może być aktywowany w całym drzewie renderowania – jest „globalny” układ. Oto możliwe przyczyny:
- Globalna zmiana stylu, która wpływa na wszystkie mechanizmy renderowania, np. zmiana rozmiaru czcionki.
- W wyniku zmiany rozmiaru ekranu
Układ może być przyrostowy, ale wyświetlane będą tylko zanieczyszczone mechanizmy renderowania (może to spowodować pewne szkody i konieczność użycia dodatkowych układów).
Układ przyrostowy jest wyzwalany (asynchronicznie), gdy mechanizmy renderowania są nieczyste. Dzieje się tak na przykład wtedy, gdy nowe mechanizmy renderowania są dodawane do drzewa renderowania po tym, jak dodatkowa treść pochodzi z sieci i została dodana do drzewa DOM.
![Układ przyrostowy.](https://web.dev/static/articles/howbrowserswork/image/incremental-layout-da3da0a148135.png?authuser=7&hl=pl)
Układ asynchroniczny i synchroniczny
Układ przyrostowy jest realizowany asynchronicznie. Firefox dodaje „polecenia przeformatowania” w przypadku układów przyrostowych, a algorytm szeregowania aktywuje zbiorcze wykonanie tych poleceń. WebKit ma również licznik czasu, który uruchamia przyrostowy układ – drzewo jest przemierzane i „brudne” mechanizmy renderowania nie mają układu.
skrypty z pytaniem o informacje o stylu, np. „offsetHeight”; może aktywować układ przyrostowy synchronicznie.
Układ globalny jest zwykle aktywowany synchronicznie.
Czasami układ jest wyzwalany jako wywołanie zwrotne po wstępnym układzie, ponieważ zmieniły się niektóre atrybuty, na przykład pozycja przewijania.
Optymalizacje
Gdy układ jest wyzwalany przez „zmianę rozmiaru” lub zmiana pozycji mechanizmu renderowania(a nie rozmiaru), rozmiary renderowania są pobierane z pamięci podręcznej i nie obliczane ponownie...
W niektórych przypadkach modyfikowane jest tylko drzewo podrzędne, a układ nie zaczyna się od poziomu głównego. Może się to zdarzyć w przypadkach, gdy zmiana jest lokalna i nie wpływa na jej otoczenie – tak jak w przypadku tekstu wstawionego w pola tekstowe (w przeciwnym razie każde naciśnięcie klawisza spowoduje uruchomienie układu, zaczynając od poziomu głównego).
Proces tworzenia układu
Układ ma zwykle następujący wzorzec:
- Nadrzędny mechanizm renderowania określa własną szerokość.
- Rodzic nadzoruje dzieci i:
- Umieść podrzędny mechanizm renderowania (ustawia jego wartości x i y).
- W razie potrzeby wywołuje układ podrzędny – są brudne, są w układzie globalnym lub z innego powodu, który oblicza wysokość dziecka.
- Element nadrzędny używa akumulujących wysokości elementów podrzędnych oraz wysokości marginesów i dopełnienia, aby ustawić własną wysokość – będzie ona używana przez element nadrzędny nadrzędnego mechanizmu renderowania.
- Ustawia jego wartość „dirty” na „false” (fałsz).
Firefox używa stanu obiekt(nsHTMLReflowState) jako parametr układu (nazywany „reflow”). Stan obejmuje między innymi szerokość elementów nadrzędnych.
Wynikiem układu Firefoksa są „dane” object(nsHTMLReflowMetrics). Będzie zawierać obliczoną wysokość przez mechanizm renderowania.
Obliczanie szerokości
Szerokość mechanizmu renderowania jest obliczana na podstawie szerokości bloku kontenera i stylu mechanizmu renderowania „width” właściwości, krawędzie i krawędzie.
Na przykład szerokość tego elementu div:
<div style="width: 30%"/>
Zostanie ona obliczona przez WebKit w następujący sposób(klasa RenderBox metoda calcWidth):
- Szerokość kontenera to maksymalna wartość kontenerów availablewidth i 0. W tym przypadku parametr availableWidth w tym przypadku to contentWidth, która jest obliczana według wzoru:
clientWidth() - paddingLeft() - paddingRight()
clientWidth i clientHeight oznaczają wnętrze obiektu; bez obramowania i paska przewijania.
Szerokość elementów to „width” (szerokość), stylu. Zostanie ona obliczona jako wartość bezwzględna poprzez obliczenie procentu szerokości kontenera.
Dodaliśmy poziome obramowanie i dopełnienie.
Jak dotąd była to „preferowana szerokość”. Teraz obliczona będzie minimalna i maksymalna szerokość.
Jeśli preferowana szerokość jest większa niż maksymalna, zostanie użyta szerokość maksymalna. Jeśli jest mniejsza niż minimalna szerokość (najmniejsza, której nie można złamać), zostanie użyta minimalna szerokość.
Wartości są zapisywane w pamięci podręcznej na wypadek, gdyby potrzebny był układ, ale szerokość się nie zmienia.
Podział wiersza
Gdy mechanizm renderowania w środku układu stwierdzi, że musi on ulec awarii, zatrzymuje się i przekazuje do elementu nadrzędnego szablonu układu, gdy trzeba go naprawić. Nadrzędny tworzy na nich dodatkowe mechanizmy renderowania i wywołuje na nich układ.
Malarstwo
Na etapie malowania korygujemy drzewo renderowania, a funkcja „paint()” mechanizmu renderowania jest wywoływana w celu wyświetlenia treści na ekranie. Obraz wykorzystuje komponent infrastruktury interfejsu użytkownika.
Globalne i przyrostowe
Podobnie jak w przypadku układów, malowanie może również mieć charakter globalny – całe drzewo jest malowane – czyli przyrostowe. Podczas 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. Powoduje to, że system operacyjny traktuje go jako „brudny region”. i wygenerować . System operacyjny robi to sprytnie i łączy kilka regionów w jeden. W Chrome jest to bardziej skomplikowane, ponieważ mechanizm renderowania działa w inny sposób niż główny proces. Chrome do pewnego stopnia symuluje działanie systemu operacyjnego. Prezentacja słucha tych zdarzeń i przekazuje wiadomość do głównego źródła renderowania. Drzewo jest przemierzane, dopóki nie zostanie osiągnięty odpowiedni mechanizm renderowania. Sama się odświeża (i zwykle swoje elementy podrzędne).
Porządek malowania
CSS2 określa kolejność procesu malowania. Jest to właściwa kolejność, w jakiej elementy są nakładane w kontekstach nakładania. To kolejność wpływa na malarstwo, ponieważ stosy są malowane od tyłu do przodu. Kolejność nakładania mechanizmu renderowania blokowego:
- background color
- obraz tła
- border
- dzieci
- konspekt
Lista wyświetlana w przeglądarce Firefox
Firefox analizuje drzewo renderowania i tworzy listę wyświetlania dla namalowanego prostokąta. Zawiera mechanizmy renderowania właściwe dla prostokąta we właściwej kolejności renderowania (tła mechanizmów renderowania, obramowanie itp.).
Dzięki temu drzewo wystarczy przejść tylko raz, a nie kilka razy – najpierw pomaluj wszystkie tła, potem wszystkie obrazy, a potem wszystkie krawędzie itd.
Firefox optymalizuje ten proces i nie dodaje elementów, które będą ukryte, np. elementów całkowicie pod innymi nieprzezroczystymi elementami.
Pamięć prostokątna WebKit
Przed ponownym renderowaniem WebKit zapisuje stary prostokąt jako mapę bitową. Następnie maluje tylko delta między nowym a starym prostokątem.
Zmiany dynamiczne
W odpowiedzi na zmianę przeglądarki starają się wykonać jak najmniejsze działania. Dlatego zmiana koloru elementu spowoduje tylko jego ponowne wyrenderowanie. Zmiany pozycji elementu spowodują jego układ oraz ponowne wyrenderowanie elementu, jego elementów podrzędnych i ewentualnie elementów potomnych. Dodanie węzła DOM spowoduje jego układ i ponowne wyrenderowanie. większe zmiany, np. zwiększenie rozmiaru czcionki w kodzie HTML. spowoduje unieważnienie pamięci podręcznych, przekazanie i ponowne wyrenderowanie całego drzewa.
Wątki silnika renderowania
Mechanizm renderowania jest jednowątkowy. Prawie wszystko (oprócz operacji sieciowych) odbywa się w jednym wątku. W Firefoksie i Safari jest to główny wątek przeglądarki. W Chrome jest to główny wątek procesu obsługi kart.
Operacje sieciowe mogą być wykonywane przez kilka równoległych wątków. Liczba połączeń równoległych jest ograniczona (zwykle jest to 2–6).
Zapętlanie zdarzeń
Wątek główny przeglądarki jest pętlą zdarzeń. To niekończąca się pętla, dzięki której ten proces jest żywy. Oczekuje na zdarzenia (np. zdarzenia układu i malowania) i je przetwarza. Oto kod głównej pętli zdarzeń w przeglądarce Firefox:
while (!mExiting)
NS_ProcessNextEvent(thread);
Model wizualny CSS2
Płótno
Zgodnie ze specyfikacją CSS2 termin canvas opisuje „obszar, w którym jest renderowana struktura formatowania”, w którym przeglądarka maluje treść.
Obszar roboczy jest nieskończony dla każdego wymiaru przestrzeni, ale przeglądarki wybierają początkową szerokość na podstawie wymiarów widocznego obszaru.
Jak podaje www.w3.org/TR/CSS2/zindex.html, obszar roboczy jest przezroczysty, jeśli znajduje się w innym miejscu, a jeśli go nie ma, nadaje się do koloru zdefiniowanego przez przeglądarkę.
Model CSS Box
Model pola CSS opisuje prostokątne pola, które są generowane dla elementów w drzewie dokumentu i rozmieszczone zgodnie z modelem formatowania wizualnego.
Każde pole ma obszar treści (np. tekst, obraz itd.) oraz opcjonalne otaczające dopełnienie, obramowania i marginesy.
![Model pola CSS2](https://web.dev/static/articles/howbrowserswork/image/css2-box-model-9c2ab852d1fb4.jpg?authuser=7&hl=pl)
Każdy węzeł generuje 0... n takich pól.
Wszystkie elementy mają parametr „display” określającą typ tworzonego pola.
Przykłady:
block: generates a block box.
inline: generates one or more inline boxes.
none: no box is generated.
Wartość domyślna jest wbudowany, ale arkusz stylów przeglądarki może zawierać inne wartości domyślne. Na przykład: domyślny sposób wyświetlania elementu „div” element jest blokowany.
Przykładowy arkusz stylów znajdziesz tutaj: www.w3.org/TR/CSS2/sample.html.
Schemat pozycjonowania
Dostępne są 3 schematy:
- Normalna: obiekt jest umieszczany zgodnie ze swoim miejscem w dokumencie. Oznacza to, że jego miejsce w drzewie renderowania jest takie samo jak jego miejsce w drzewie DOM i ułożone zgodnie z typem ramki i wymiarami.
- Unoszący się: obiekt jest najpierw rozłożony tak jak zwykle, a następnie został przesunięty jak najdalej w lewo lub w prawo.
- Bezwzględne: obiekt jest umieszczany w drzewie renderowania w innym miejscu niż w drzewie DOM.
Schemat pozycjonowania jest ustalany przez parametr „position”, a właściwością „float” .
- statyczne i względne powodują normalny przepływ
- pozycjonowanie bezwzględne i stałe
W przypadku pozycjonowania statycznego nie jest zdefiniowana żadna pozycja i używane jest pozycjonowanie domyślne. W innych schematach to autor określa położenie: góra, dół, lewo, prawo.
Układ skrzynki zależy od tych czynników:
- Typ pola
- Wymiary ramki
- Schemat pozycjonowania
- informacje zewnętrzne, takie jak rozmiar obrazu i rozmiar ekranu;
Typy pól
Pole bloków: tworzy bryłę – w oknie przeglądarki znajduje się własny prostokąt.
![Zablokuj.](https://web.dev/static/articles/howbrowserswork/image/block-box-b5c0bff4a44d2.png?authuser=7&hl=pl)
Wbudowana ramka: nie ma własnego bloku, ale znajduje się wewnątrz bloku.
![Wbudowane pola.](https://web.dev/static/articles/howbrowserswork/image/inline-boxes-a9ff03002e7e2.png?authuser=7&hl=pl)
Bloki są sformatowane w pionie jeden po drugim. Wiersze w tekście są sformatowane w poziomie.
![Formatowanie blokowe i wbudowane.](https://web.dev/static/articles/howbrowserswork/image/block-inline-formatting-a8450c63cf457.png?authuser=7&hl=pl)
Ramki wbudowane umieszcza się w wierszach. Linie mają co najmniej taką samą wysokość, jak najwyższa ramka, ale mogą być wyższe, gdy pola są wyrównane do wartości odniesienia – oznacza to, że dolna część elementu jest wyrównana względem punktu innej ramki niż dolna. Jeśli szerokość kontenera jest niewystarczająca, elementy wewnętrzne zostaną umieszczone w kilku wierszach. Zwykle dzieje się tak w akapicie.
![Linie.](https://web.dev/static/articles/howbrowserswork/image/lines-68c55a378a7ff.png?authuser=7&hl=pl)
Pozycjonowanie
Krewny
Pozycjonowanie względne – pozycjonowanie w zwykły sposób, a następnie przesunięcie o wymaganą wartość delta.
![Pozycjonowanie względne.](https://web.dev/static/articles/howbrowserswork/image/relative-positioning-fceed0670b8c5.png?authuser=7&hl=pl)
Swobodne
Pole zmiennoprzecinkowe jest przesuwane na lewo lub prawo od linii. Ciekawe jest to, że wokół niego poruszają się inne pola. Kod HTML:
<p>
<img style="float: right" src="images/image.gif" width="100" height="100">
Lorem ipsum dolor sit amet, consectetuer...
</p>
Będzie to:
![Unoszenie.](https://web.dev/static/articles/howbrowserswork/image/float-dd9b790210284.png?authuser=7&hl=pl)
Wartości bezwzględne i stałe
Układ jest zdefiniowany dokładnie niezależnie od normalnego przepływu. Element nie uczestniczy w zwykłym przepływie. Wymiary odnoszą się do kontenera. W przypadku elementów stałych kontenerem jest widoczny obszar.
![Stałe pozycjonowanie.](https://web.dev/static/articles/howbrowserswork/image/fixed-positioning-881933fcb1a2f.png?authuser=7&hl=pl)
Reprezentacja warstwowa
Określa ją właściwość CSS z-index. Reprezentuje trzeci wymiar pola: jego położenie na osi „z”.
Pudełka są podzielone na stosy (nazywane kontekstami nakładania). W każdym stosie elementy tylne będą najpierw namalowane, a elementy przednie u góry, bliżej użytkownika. W przypadku nakładania się pierwszy element ukryje poprzedni.
Stosy są uporządkowane zgodnie z właściwością z-index. Ramki z „z-index” tworzyć lokalny stos. Widoczny obszar ma 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 taki:
![Stałe pozycjonowanie.](https://web.dev/static/articles/howbrowserswork/image/fixed-positioning-84a46b366dc9a.png?authuser=7&hl=pl)
Chociaż czerwony element div w znaczniku występuje przed zielonym i byłby namalowany w ramach zwykłego procesu, to właściwość z-index jest wyższa, więc znajduje się bardziej na przód w stosie trzymanym przez ramkę główną.
Zasoby
Architektura przeglądarki
- Grosskurth, Alan. Architektura referencyjna dla przeglądarek internetowych (pdf)
- Gupta, Winet. Jak działają przeglądarki – część 1 – architektura
Analiza
- Aho, Sethi, Ullman, Compilers: Principles, Techniques, and Tools (inaczej „książka o smokach”), Addison-Wesley, 1986 r.
- Ricka Jelliffe'a. Piękne i pogrubione: 2 nowe wersje robocze HTML 5.
Firefox
- L. David Baron, Faster HTML and CSS: Layout Engine Internals for Web Developers.
- L. David Baron, Faster HTML and CSS: Layout Engine Internals for Web Developers (Google tech talk video)
- L. David Baron, silnik układu Mozilla's
- L. David Baron, dokumentacja systemu Mozilla Style
- Chris Waterson, Notes on HTML Reflow
- Chris Waterson, Gecko Overview
- Alexander Larsson, Jak działa żądanie HTTP HTML
WebKit
- David Hyatt, Wdrażanie CSS(część 1)
- David Hyatt, An Overview of WebCore
- David Hyatt, WebCore Rendering
- David Hyatt, The FOUC Problem
Specyfikacje W3C
Instrukcje kompilacji przeglądarek
Tłumaczenia
Ta strona została przetłumaczona na język japoński dwukrotnie:
- Jak działają przeglądarki – za kulisami nowoczesnych przeglądarek (ja) autor: @kosei
- ブラウザってどうやって動いてるの?(モダンWEBブラウザシーンの裏側 autor: @ikeike443 i @kiyoto01.
Możesz wyświetlić hostowane zewnętrznie tłumaczenia: koreański i turecki.
Dziękuję wszystkim!