<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" href="/feeds/stylesheet.xsl"?>
<feed xmlns="http://www.w3.org/2005/Atom">
	
	
	<link href="https://blog.comandeer.pl/feeds/kategorie/adwent-2025.xml" rel="self" type="application/atom+xml"/>
	<link href="https://blog.comandeer.pl/kategorie/adwent-2025" rel="alternate" type="text/html"/>
	<updated>2026-04-25T11:06:04.890Z</updated>
	<id>https://blog.comandeer.pl/feeds/kategorie/adwent-2025.xml</id>
	<title type="html">Comandeerowy blog</title>
	
		<category term="Adwent 2025"/>
		<subtitle>Kategoria Adwent 2025</subtitle>
	
	
		
	
	
		<entry>
			<title type="html">Nullius in verba</title>
			
				<author>
					<name>Comandeer</name>
				</author>
			
			<link href="https://blog.comandeer.pl/nullius-in-verba" rel="alternate" type="text/html"/>
			<published>2025-12-23T23:06:00.000Z</published>
			<updated>2025-12-23T23:06:00.000Z</updated>
			<id>https://blog.comandeer.pl/nullius-in-verba</id>
			
				<summary><![CDATA[A co, gdybyśmy traktowali webdev bardziej jak naukę?]]></summary>
			
			<content type="html"><![CDATA[<p>Osoby nieco dłużej śledzące moją internetową aktywność mogły zauważyć pewną zmianę mojego podejścia do tematów webdevowych. W mojej dawnej webkrytykowej twórczości, ale i w początkach istnienia tego bloga, o wiele więcej było, hm, <em>emocjonalnych</em> wstawek. Niczym dekadencki poeta z bujną i rozwichrzoną czupryną zdecydowanie stawiałem formę wyżej od treści. Obecnie moje podejście jest zdecydowanie bardziej <em>naukowe</em>. I to bynajmniej nie z powodu nagłego braku bujnej czupryny!</p>
<h2 id="podejście-naukowe"><a class="header-anchor" href="https://blog.comandeer.pl/nullius-in-verba#podejście-naukowe">Podejście naukowe</a></h2>
<p>Co to jednak znaczy, że traktuję webdev naukowo? Tak naprawdę wyróżniłbym tutaj trzy główne elementy:</p>
<ol>
<li>formę treści,</li>
<li>obecność źródeł,</li>
<li>empiryczność.</li>
</ol>
<h3 id="forma-treści"><a class="header-anchor" href="https://blog.comandeer.pl/nullius-in-verba#forma-treści">Forma treści</a></h3>
<p>Artykuły naukowe mają ściśle określoną strukturę. Bardzo często opisuje się ją <a href="https://en.wikipedia.org/wiki/IMRAD" rel="noreferrer noopener">akronimem IMRaD</a>:</p>
<ol>
<li><b lang="en">Introduction</b> (wprowadzenie) – wyjaśnienie celu badania, postawienie hipotezy,</li>
<li><b lang="en">Methods</b> (metody badawcze) – w jaki sposób badanie zostało wykonane, kto w nim uczestniczył,</li>
<li><b lang="en">Results</b> (wyniki) – omówienie wyników badania, sprawdzenie, czy wyniki zgadzają się&nbsp;z postawioną hipotezą,</li>
<li><b lang="en">Discussion</b> (polemika) – próba interpretacji wyników, porównanie ich z innymi badaniami z tego zakresu, zaproponowanie możliwych przyszłych kierunków badań.</li>
</ol>
<p>Tak tworzone artykuły powinny zachowywać jak największą obiektywność, skupiając się przede wszystkim na opisie faktów, z pominięciem opinii osób autorskich.</p>
<p>Ja nie jestem naukowcem i siłą mojego bloga jest jednak to, że umieszczam na nim materiały o webdevie okraszone <em>moją</em> opinią. Więc bardziej nazwałbym swoją radosną twórczość <dfn id="esej-hipertekstowy">esejami hipertekstowymi</dfn>. To nic innego jak <a href="https://pl.wikipedia.org/wiki/Esej" rel="noreferrer noopener">eseje</a>, które są opublikowane w formie hipertekstu. Czyli: mogą linkować do innych esejów lub bezpośrednio do źródeł, Równocześnie jednak – raz luźniej, raz ściślej – trzymam się struktury IMRaD. Weźmy taki <a href="https://blog.comandeer.pl/pudelko-z-ciasteczkami">artykuł o Cookie Store API</a>. Na samym początku jest wprowadzenie, w którym zaznaczam problem (<q>relacja Sieci z ciasteczkami od zawsze była skomplikowana</q>). Następnie następuje omówienie wyników badania (w tym wypadku: organoleptycznego sprawdzenia, jak się obsługuje ciasteczka w przeglądarkach). W przypadku artykułów z eksperymentów (jak np. <a href="https://blog.comandeer.pl/jednoplikowe-komponenty">jednoplikowych komponentów</a> czy <a href="https://blog.comandeer.pl/ascss">ASCSS-a</a>) pojawiają się&nbsp;też sekcje z metodami (opis inspiracji i narzędzi użytych do stworzenia danego projektu) oraz polemika (w jaki sposób go dalej rozwijać, czy moje pierwotne założenia udało się spełnić, itd.).</p>
<h3 id="źródła"><a class="header-anchor" href="https://blog.comandeer.pl/nullius-in-verba#źródła">Źródła</a></h3>
<p>Niemniej to nie forma jest głównym wyznacznikiem “naukowości”. Według mnie o wiele ważniejszym jest odwoływanie się do źródeł. W końcu szansa, że opisałem coś jako pierwszy w Internecie, jest niemalże zerowa. Nawet jeśli w swoim zakątku Sieci jestem faktycznie pierwszy, to przecież o jakimś standardzie sieciowym napisała przede mną choćby… osoba pisząca jego specyfikację. Nawet jeśli już wymyślę&nbsp;coś absolutnie swojego (jak <a href="https://blog.comandeer.pl/headings-first-principle"><span lang="en">Headings First Principle</span> (HFP)</a>), to przecież to nie istnieje w próżni. Żeby mogło powstać HFP, musiał powstać HTML, a w nim nagłówki. Niezbędnymi elementami były też wszelkie <a href="https://www.smashingmagazine.com/2022/07/article-section-elements-accessibility/" rel="noreferrer noopener">dobre praktyki wokół dzielenia treści stron WWW na sekcje</a> oraz <a href="https://webaim.org/projects/screenreadersurvey10/#finding" rel="noreferrer noopener">sposób nawigowania przy pomocy nagłówków przez osoby korzystające z czytników ekranowych</a>. Słowem: musiał istnieć cały skomplikowany ekosystem Sieci, żebym ja mógł do niego dodać swoją małą&nbsp;cegiełkę.</p>
<p>Chyba najdobitniej widać to w przypadku mojego, jak dotąd najambitniejszego, projektu, <a href="https://gwd.comandeer.pl/" rel="noreferrer noopener">GWD</a>. W każdym “rozdziale” na końcu znajdują&nbsp;się listy źródeł oraz dodatkowych materiałów. Nazbierało się ich <strong>około tysiąca</strong>. A i tak była to mocno okrojona lista. Zwłaszcza, że sporo tematów ostatecznie wypadło z tego eseju.</p>
<p>Niemniej samo odesłanie do źródeł to za mało. Prawdziwa praca polega na odpowiednim ich dobraniu. Bo można opisać np. element HTML <code>bdo</code> i odesłać do jakiejś strony-krzak czy <a href="https://www.webkrytyk.pl/2022/10/30/w3schools-com-runda-druga/" rel="noreferrer noopener">popularnej, acz słabej jakości witryny</a>. Zamiast tego wypada jednak dokładnie zapoznać się, do czego linkujemy, czy nie ma tam błędów merytorycznych, czy poruszone są wszystkie kwestie ważne poruszenia, itd. I tak jak w świecie nauki, w którym mamy tytuły godne zaufania (<a href="https://www.science.org/" rel="noreferrer noopener">Science</a>, <a href="https://www.nature.com/" rel="noreferrer noopener">Nature</a>), tak i w webdevie jedne źródła są bezpieczniejsze od innych. Najbezpieczniejsze są, rzecz jasna, specyfikacje i standardy. Nie ma pewniejszych źródeł od nich. Jeśli chcę opisać element <code>bdo</code>, to <a href="https://html.spec.whatwg.org/multipage/text-level-semantics.html#the-bdo-element" rel="noreferrer noopener">specyfikacja HTML</a> jest Kanonicznym Źródłem Prawdy™. Ale standardy często są pisane w sposób całkowicie hermetyczny i niezrozumiały dla dowolnej osoby, która nie czyta sobie RFC do poduszki. Wtedy warto sięgnąć po drugie najlepsze źródło – <a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/bdo" rel="noreferrer noopener">dokumentację MDN</a>. Jeśli coś można by nazwać <q>oficjalną dokumentacją platformy sieciowej</q>, to byłoby to właśnie MDN.</p>
<p>Dobrej jakości źródeł jest oczywiście więcej – jak choćby <a href="https://www.smashingmagazine.com/" rel="noreferrer noopener">Smashing Magazine</a>, <a href="https://alistapart.com/" rel="noreferrer noopener">A List Apart</a> czy blogi znanych osób ze światka webdevu. I często na tych stronach, oderwanych od “oficjalnego obiegu”, dzieją się rzeczy ciekawsze. To w końcu na łamach A List Apart powstał termin <a href="https://alistapart.com/article/responsive-web-design/" rel="noreferrer noopener">Responsive Web Design</a>! Często też wyłuskać można na jakiejś stronie jakąś perełkę wśród całej reszty <em>meh</em> artykułów (hej, to zupełnie jak na moim blogu!). Tylko no właśnie – <em>wyłuskać</em>…</p>
<p>Sprawne posługiwanie się źródłami wymaga mimo wszystko wprawy i pewnego doświadczenia. Ja w swoim życiu przerobiłem już <em>tysiące</em> artykułów o webdevie i jestem w stanie oddzielić te faktycznie ciekawe od tych, które takie nie są. I właśnie tę umiejętność selekcji uważam za kluczową w bardziej naukowym podejściu do webdevu. Żeby ją zdobyć, to nie ma innej rady – trzeba spędzić swoje na czytaniu. Na początku będzie się czytać wszystko, jak leci, by z czasem odrzucać te najgorsze z najgorszych, aż w końcu nadejdzie moment, w którym się Po Prostu Wie™, co jest warte poświęcenia czasu, a co nie.</p>
<p>Niemniej z selekcją źródeł jest jeszcze jeden mały haczyk: ryzyko wpadnięcia w bańkę. Każda osoba ma jakieś poglądy i przekonania. Choćby tak błahe, jak nielubienie Reacta czy skłonność do wybierania mniej popularnych rozwiązań. I takie przekonania będą – bardziej lub mniej świadomie – wpływać na to, które źródła się dobiera. Nie sądzę, by dało się od tego całkowicie uciec, ale im bardziej się jest tego świadomym, tym mniejszy ma to wpływ na proces selekcji. Bo czasami warto wręcz sięgać po źródła, z którymi się nie zgadzamy – choćby po to, by <a href="https://gwd.comandeer.pl/progressive-enhancement/#krytyka" rel="noreferrer noopener">wejść&nbsp;z nimi w polemikę</a>.</p>
<h3 id="empiryczność"><a class="header-anchor" href="https://blog.comandeer.pl/nullius-in-verba#empiryczność">Empiryczność</a></h3>
<p>Wreszcie ostatni element, który widać w większości moich artykułów – zarówno tutaj, jak i <a href="https://www.webkrytyk.pl/2021/10/31/wpadki-i-wypadki-14/" rel="noreferrer noopener">na WebKrytyku</a>. Empiryczność, czyli mówiąc inaczej: eksperymentowanie. Kiedy opisuję nowe API, fajnie byłoby, gdybym przygotował choćby krótkie demko, w którym bym zademonstrował jego podstawowe ficzery. Tym prostym sposobem można być jak Longinus Podbipięta i <s>ściąć trzy gł</s> odhaczyć trzy rzeczy:</p>
<ol>
<li>pokazujemy osobom czytającym,&nbsp;że opisywane przez nas rzeczy faktycznie istnieją i działają,</li>
<li>tworzymy dla siebie samych z przyszłości źródło do linkowania, jeśli kiedyś wspomnimy znowu o tym API,</li>
<li>zapamiętujemy całą rzecz lepiej, niż gdybyśmy naskrobali jedynie teoretyczny opis.</li>
</ol>
<p>Poza tym – ostatecznie piszę ten blog dla przyjemności. A gdzie byłby cały fun z tego, gdybym się nie bawił kodem?</p>
<h2 id="tylko-po-co"><a class="header-anchor" href="https://blog.comandeer.pl/nullius-in-verba#tylko-po-co">Tylko po co?</a></h2>
<p>Odpowiedziałem już na pytanie, <q>jak</q> traktować webdev bardziej naukowo. Pora zatem pochylić się nad drugim, równie ważnym – jeśli nawet nie ważniejszym – pytaniem: <q>po co</q>?</p>
<p>Bo uważam, że tego wymaga uczciwość&nbsp;intelektualna. Webdev nie jest jakąś bliżej niezdefiniowaną sztuką okultystyczną (mimo że czasami tak się jawi!). Wbrew pozorom zdecydowana większość platformy sieciowej jest oparta na konkretnych standardach i specyfikacjach. Bo gdyby nie była, strony WWW działałyby w jednych przeglądarkach i nie działały w innych. <a href="https://thehistoryoftheweb.com/browser-wars/" rel="noreferrer noopener">Te czasy już przerabialiśmy</a> i to właśnie dzięki nim dzisiaj mamy otwarte standardy sieciowe. Jasne, nie jest idealnie, ale inicjatywy takie jak <a href="https://wpt.fyi/" rel="noreferrer noopener">Web Platform Tests</a> czy <a href="https://web.dev/blog/interop-2025" rel="noreferrer noopener">Interop</a> dają nadzieję, ze będzie lepiej. A skoro Sieć oparta jest na fundamentach, które są skodyfikowane (spisane “na papierze”, z przybitą pieczątką) i możliwe do empirycznej weryfikacji (w każdej chwili mogę odpalić przeglądarkę i sprawdzić, jak ten HTML czy CSS działają), to nie widzę powodu, by moje artykuły się do tego nie odwoływały.</p>
<p>Uczciwość intelektualna wymaga też, by istniała pewna ciągłość między tym, co tworzymy, a tym, co zostało stworzone. Tak jak artykuł naukowy nie może istnieć bez źródeł, tak trudno stworzyć artykuł o hipertekście bez… hipertekstu. W webdevie nie ma <a href="https://en.wikipedia.org/wiki/Self-made_man" rel="noreferrer noopener"><i lang="en">self-made men</i></a>, wszyscy budujemy na fundamentach postawionych przez innych. Nawet jeśli nasze budowanie polega na niezgadzaniu się z już istniejącymi dobrymi praktykami i proponowaniu czegoś nowego. Bo ostatecznie nie byłoby tej propozycji, gdyby nie ta praktyka! Żeby się z czymś nie zgadzać, trzeba najpierw zauważyć istnienie tego czegoś. Inaczej <a href="https://www.webkrytyk.pl/2021/12/15/video-swiete-wojny-javascriptu-ile-h1-na-stronie-i-dlaczego-kuba-przespal-ostatnie-10-lat/" rel="noreferrer noopener">nasza krytyka będzie całkowicie jałowa</a>.</p>
<p>Wreszcie: webdev najzwyczajniej w świecie <em>zasługuje</em> na bardziej naukowe – czy też po prostu merytoryczne – traktowanie. Choćby dlatego, że Sieć stanowi coraz większą część&nbsp;naszego codziennego życia. To wszechobecna technologia, do której <a href="https://www.itu.int/en/mediacentre/Pages/PR-2025-11-17-Facts-and-Figures.aspx" rel="noreferrer noopener">dostęp ma <strong>ok. 6 miliardów ludzi</strong></a>. W innych dziedzinach nauki i technologii, które często dotykają mniejszej liczby osób (jak np. projekt nowego skafandra astronautycznego), zachowujemy niezwykłą precyzję i naukową rygorystyczność. Natomiast w dziedzinie, która w taki czy inny sposób zahacza o <em>jakieś ¾ światowej populacji</em>, mam wrażenie, że często wychodzimy z założenia, że <q>to tylko technologia</q>. A to przecież nieprawda. <a href="https://shkspr.mobi/blog/2021/01/the-unreasonable-effectiveness-of-simple-html/" rel="noreferrer noopener">Ludzie mają różne powody, by korzystać z Internetu</a>. Jeśli – jako osoby tworzące strony WWW – nie traktujemy Sieci wystarczająco poważnie, ostatecznie szkodzimy osobom, które z tych stron WWW następnie korzystają. I myślę, że przestawienie myślenia z <q>to tylko technologia</q> w kierunku <q>webdev to nauka</q> jest mało bolesnym sposobem na stworzenie lepszej Sieci.</p>
<p>I tym optymistycznym akcentem życzę Wszystkim wesołych świąt 🎄🎄🎄!</p>
]]></content>
		</entry>
	
		<entry>
			<title type="html">Dobry ziomek system</title>
			
				<author>
					<name>Comandeer</name>
				</author>
			
			<link href="https://blog.comandeer.pl/dobry-ziomek-system" rel="alternate" type="text/html"/>
			<published>2025-12-22T23:00:00.000Z</published>
			<updated>2025-12-22T23:00:00.000Z</updated>
			<id>https://blog.comandeer.pl/dobry-ziomek-system</id>
			
				<summary><![CDATA[Systemy operacyjne wcale nie są takie najgorsze, jeśli chodzi o dostępność.]]></summary>
			
			<content type="html"><![CDATA[<p>Dzisiaj chciałbym zwrócić uwagę na cichego bohatera dostępności: system operacyjny! Dostarcza on bowiem szeregu technologii asystujących oraz innych usprawnień dla osób z niepełnosprawnościami.</p>
<h2 id="technologia-asystująca"><a class="header-anchor" href="https://blog.comandeer.pl/dobry-ziomek-system#technologia-asystująca">Technologia asystująca</a></h2>
<p>Czym jednak jest technologia asystująca? Za <a href="https://www.who.int/news-room/fact-sheets/detail/assistive-technology" rel="noreferrer noopener">definicją proponowaną przez WHO</a>:</p>
<blockquote>
<p lang="en">Assistive products help maintain or improve an individual’s functioning related to cognition, communication, hearing, mobility, self-care and vision, thus enabling their health, well-being, inclusion and participation.</p>
<p>[Produkty asystujące pomagają w utrzymaniu lub poprawie funkcjonowania osoby w zakresie funkcji poznawczych, komunikacji, słuchu, mobilności, samoopieki oraz wzroku, pozwalając tej osobie być zdrową fizycznie i psychicznie, włączoną w życie społeczne oraz uczestniczącą w nim.]</p>
</blockquote>
<p>Ta definicja jest niezwykle szeroka i obejmuje tak naprawdę wszystkie sprzęty oraz oprogramowanie, które pomagają w codziennym funkcjonowaniu. Stąd można do niej zaliczyć np. wózek inwalidzki, czytniki ekranu, ale też choćby lupę. Swego czasu <a href="https://gwd.comandeer.pl/dostepnosc/technologia-asystujaca/" rel="noreferrer noopener">opisałem trochę przykładów takich technologii</a>.</p>
<h2 id="systemowa-technologia-asystująca"><a class="header-anchor" href="https://blog.comandeer.pl/dobry-ziomek-system#systemowa-technologia-asystująca">Systemowa technologia asystująca</a></h2>
<p>Systemy operacyjne dostarczają osobom z nich korzystających wbudowanych technologii asystujących. Lista takich technologii będzie się różnić w zależności od systemu. Weźmy jako przykład macOS-a. Chyba najbardziej znaną technologią asystującą dostępną wraz z tym systemem jest <a href="https://en.wikipedia.org/wiki/VoiceOver" rel="noreferrer noopener">czytnik ekranowy VoiceOver</a>. Nie jest bynajmniej jedyną. Ustawienia systemu posiadają mocno rozbudowaną sekcję poświęconą dostępności:</p>
<figure class="figure"><a class="figure__link" href="/assets/images/dobry-ziomek-system/macos-ustawienia-1360w.avif"><picture><source type="image/avif" srcset="/assets/images/dobry-ziomek-system/macos-ustawienia-440w.avif 440w, /assets/images/dobry-ziomek-system/macos-ustawienia-880w.avif 880w, /assets/images/dobry-ziomek-system/macos-ustawienia-1024w.avif 1024w, /assets/images/dobry-ziomek-system/macos-ustawienia-1360w.avif 1360w" sizes="90vw"><source type="image/webp" srcset="/assets/images/dobry-ziomek-system/macos-ustawienia-440w.webp 440w, /assets/images/dobry-ziomek-system/macos-ustawienia-880w.webp 880w, /assets/images/dobry-ziomek-system/macos-ustawienia-1024w.webp 1024w, /assets/images/dobry-ziomek-system/macos-ustawienia-1360w.webp 1360w" sizes="90vw"><source type="image/jpeg" srcset="/assets/images/dobry-ziomek-system/macos-ustawienia-440w.jpeg 440w, /assets/images/dobry-ziomek-system/macos-ustawienia-880w.jpeg 880w, /assets/images/dobry-ziomek-system/macos-ustawienia-1024w.jpeg 1024w, /assets/images/dobry-ziomek-system/macos-ustawienia-1360w.jpeg 1360w" sizes="90vw"><img src="/assets/images/dobry-ziomek-system/macos-ustawienia-1360w.jpeg" width="1360" height="1200" style="aspect-ratio: 1360 / 1200" alt="Aplikacja Ustawienia, zakładka &quot;Accessibility&quot;;&nbsp;w sekcji &quot;Vision&quot;&nbsp;znajdują się opcje &quot;VoiceOver&quot;, &quot;Zoom&quot;, &quot;Hover Text&quot;, &quot;Display&quot;, &quot;Motion&quot;, &quot;Read &amp; Speak&quot;, &quot;Audio Descriptions&quot;; w sekcji &quot;Hearing&quot;&nbsp;znajdują się opcje &quot;Hearing Devices&quot; oraz &quot;Audio&quot;; więcej opcji jest niewidocznych." loading="lazy" decoding="async"></picture></a><figcaption class="figure__caption"><p>Sekcja ustawień dostępności</p></figcaption></figure>
<p>Jak widać, macOS dzieli te ustawienia na poszczególne zmysły, które dane opcje mają wspierać. Stąd w kategorii wzroku mamy <a href="http://m.in" rel="noreferrer noopener">m.in</a>. czytnik ekranu (VoiceOver), ale też ustawienia wyświetlacza czy animacji ruchowych, w słuchu – ustawienia dźwięku itd. Każda z tych opcji w menu posiada konkretne ustawienia, np. odnośnie animacji ruchowych:</p>
<figure class="figure"><a class="figure__link" href="/assets/images/dobry-ziomek-system/macos-ruch-1360w.avif"><picture><source type="image/avif" srcset="/assets/images/dobry-ziomek-system/macos-ruch-440w.avif 440w, /assets/images/dobry-ziomek-system/macos-ruch-880w.avif 880w, /assets/images/dobry-ziomek-system/macos-ruch-1024w.avif 1024w, /assets/images/dobry-ziomek-system/macos-ruch-1360w.avif 1360w" sizes="90vw"><source type="image/webp" srcset="/assets/images/dobry-ziomek-system/macos-ruch-440w.webp 440w, /assets/images/dobry-ziomek-system/macos-ruch-880w.webp 880w, /assets/images/dobry-ziomek-system/macos-ruch-1024w.webp 1024w, /assets/images/dobry-ziomek-system/macos-ruch-1360w.webp 1360w" sizes="90vw"><source type="image/jpeg" srcset="/assets/images/dobry-ziomek-system/macos-ruch-440w.jpeg 440w, /assets/images/dobry-ziomek-system/macos-ruch-880w.jpeg 880w, /assets/images/dobry-ziomek-system/macos-ruch-1024w.jpeg 1024w, /assets/images/dobry-ziomek-system/macos-ruch-1360w.jpeg 1360w" sizes="90vw"><img src="/assets/images/dobry-ziomek-system/macos-ruch-1360w.jpeg" width="1360" height="1200" style="aspect-ratio: 1360 / 1200" alt="Aplikacja Ustawienia, zakładka &quot;Accessibility&quot;, podsekcja &quot;Motion&quot;; dostępne utawienia: &quot;Reduce motion&quot;, &quot;Dim flashing lights&quot;, &quot;Auto-play animated images&quot;, &quot;Prefer non-blinking cursor&quot;, &quot;Vehicle Motion Cues&quot;." loading="lazy" decoding="async"></picture></a><figcaption class="figure__caption"><p>Sekcja ustawień dostępności dotyczących animacji ruchowych</p></figcaption></figure>
<p>System od Apple pozwala na dostosowanie większości aspektów systemu, np. wyłączając autoodtwarzanie ruchomych obrazków (GIF-ów). I choć na początku ogrom opcji może przytłaczać, to sam fakt ich istnienia jest jak najbardziej pozytywny. W końcu jedne z najbardziej dostępnych rozwiązań to te, które można <a href="https://gwd.comandeer.pl/inkluzywnosc/preferencje-osoby-uzytkowniczej/" rel="noreferrer noopener">najbardziej dostosować pod siebie</a>!</p>
<p>Windows również posiada dość rozbudowane opcje dostępności. Podobnie jak macOS udostępnia czytnik ekranowy, <a href="https://www.microsoft.com/en-us/windows/tips/narrator" rel="noreferrer noopener">Narratora</a>. W przypadku jednak tego systemu nie zdobył on nigdy większej popularności. Od lat na tej platformie królują <a href="https://vispero.com/jaws-screen-reader-software/" rel="noreferrer noopener">JAWS</a> (płatny) oraz <a href="https://www.nvaccess.org/about-nvda/" rel="noreferrer noopener">NVDA</a> (darmowy). Oprócz czytnika Windows oferuje choćby <a href="https://support.microsoft.com/pl-pl/windows/u%C5%BCywanie-lupy-do-zapewnienia-lepszej-widoczno%C5%9Bci-element%C3%B3w-na-ekranie-414948ba-8b1c-d3bd-8615-0e5e32204198" rel="noreferrer noopener">lupę ekranową</a> (do powiększania fragmentów ekranu) czy <a href="https://blog.comandeer.pl/czy-div-jest-dostepny#dost%C4%99pno%C5%9B%C4%87-to-nie-tylko-czytniki-ekranowe">tryb wysokiego kontrastu</a>.</p>
<p>Systemy mobilne także dostarczają technologii asystujących. Zarówno Android, jak i iOS, mają wbudowane czytniki ekranowe – odpowiednio <a href="https://support.google.com/accessibility/android/answer/6007100?hl=en" rel="noreferrer noopener">TalkBack</a> i VoiceOver. Mają także choćby opcję ograniczania animacji ruchowych. Także sporo linuksowych dystrybucji ma ustawienia dostępności, np. LInux Mint pozwala włączyć tryb wysokiego kontrastu i ma wbudowany czytnik ekranu (<a href="https://orca.gnome.org/" rel="noreferrer noopener">Orcę</a>).</p>
<p>Innymi słowy: systemy operacyjne próbują dostarczyć niezbędnych narzędzi do tego, by mogło z nich korzystać jak najwięcej osób, w tym osoby z niepełnosprawnościami.</p>
<h2 id="system-a-przeglądarka"><a class="header-anchor" href="https://blog.comandeer.pl/dobry-ziomek-system#system-a-przeglądarka">System a przeglądarka</a></h2>
<p>Jak zatem taka systemowa technologia asystująca przekłada się na przeglądarkę? To zależy. Wraz z rozwojem technologii sieciowych, osoby tworzące strony WWW dostały możliwość dowiedzenia się nieco więcej o systemie operacyjnym, na którym uruchomiona jest przeglądarka. Najczęściej dzięki nowym media queries w CSS-ie. Wśród nich wymienić można:</p>
<ul>
<li><a href="https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/At-rules/@media/prefers-reduced-motion" rel="noreferrer noopener"><code>prefers-reduced-motion</code></a> – informujące o ustawieniach dotyczących ograniczenia animacji ruchowych,</li>
<li><a href="https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/At-rules/@media/forced-colors" rel="noreferrer noopener"><code>forced-colors</code></a> – informujące o trybie wymuszonych kolorów, czyli ograniczeniu palety dostępnych kolorów, jak w przypadku Windowsowego trybu wysokiego kontrastu,</li>
<li><a href="https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/At-rules/@media/prefers-color-scheme" rel="noreferrer noopener"><code>prefers-color-scheme</code></a> – informujące o preferencjach osoby użytkowniczej co do doboru schematu kolorystycznego; inaczej mówiąc: czy woli jasny, czy ciemny motyw.</li>
</ul>
<p>Część z systemowych ułatwień dostępu może być jednak “zakamuflowana”. Dobrym przykładem są tu różnego rodzaju powiększenia. Działają one trochę jak zmniejszenie rozdzielczości: poszczególne elementy robią się większe, przez co są bardziej czytelne. Równocześnie jednak ogranicza to liczbę elementów widocznych w danej chwili na ekranie. Przeglądarka często interpretuje to jako mniejszy <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/Guides/CSSOM_view/Viewport_concepts" rel="noreferrer noopener">viewport</a>. Co też dobrze pokazuje, jak zawodne jest opieranie responsywności na założeniu <q>mały ekran = urządzenie mobilne</q>.</p>
<p>Jeszcze inne technologie asystujące są całkowicie przezroczyste dla przeglądarki, jak np. czytniki ekranu. Nie ma żadnego sposobu, aby dowiedzieć się, że osoba odwiedzająca stronę używa czytnika ekranu. I to <a href="https://axesslab.com/digital-apartheid/" rel="noreferrer noopener">akurat dobrze</a>. Zwłaszcza, że stworzenie strony z zachowaniem standardów sieciowych i dobrych praktyk dostępności jest w większości przypadków wystarczające, by strona ta działała poprawnie we wszystkich popularnych czytnikach ekranu. Innymi słowy: wystarczy <em>dobrze</em> wykonywać swoją pracę – jakkolwiek brutalnie by to nie brzmiało.</p>
<p>Podsumowując: przeglądarka i system to para zaufanych przyjaciół, która niektóre sekrety zostawia wyłącznie dla siebie. Ale to, czym się jednak z nami dzielą, powinno wystarczyć do stworzenia dostępnej, przyjaznej strony WWW.</p>
]]></content>
		</entry>
	
		<entry>
			<title type="html">Ale kanał…</title>
			
				<author>
					<name>Comandeer</name>
				</author>
			
			<link href="https://blog.comandeer.pl/ale-kanal" rel="alternate" type="text/html"/>
			<published>2025-12-21T23:00:00.000Z</published>
			<updated>2025-12-21T23:00:00.000Z</updated>
			<id>https://blog.comandeer.pl/ale-kanal</id>
			
				<summary><![CDATA[Pora nieco ulepszyć kolejny element bloga – kanały Atom.]]></summary>
			
			<content type="html"><![CDATA[<p>Dzisiaj nadeszła pora na kolejny element bloga, który już od dłuższego czasu czekał na poprawki: kanały Atom.</p>
<h2 id="kanały-atom"><a class="header-anchor" href="https://blog.comandeer.pl/ale-kanal#kanały-atom">Kanały Atom</a></h2>
<p>Dawno, dawno temu ktoś wpadł na szalony pomysł: a co jeśliby pozwolić ludziom czytać treść ze stron internetowych <em>jak i kiedy chcą</em>? Zamiast musieć wchodzić na konkretną&nbsp;stronę WWW, ktoś mógłby po prostu dostać informację o nowym wpisie na blogu czy zdjęciu w galerii podobnie, jak dostaje maila. Ot, taki newsletter, tylko że nie przez maila. Tak powstały tzw. <a href="https://en.wikipedia.org/wiki/Web_feed" rel="noreferrer noopener"><i lang="en">feeds</i> (kanały)</a>. W największym skrócie: kanał to plik w formacie innym niż HTML, w którym znajduje się&nbsp;treść strony i który jest przystosowany do czytania przez tzw. <i lang="en">feed readers</i> (czytniki kanałów). Te czytniki pobierają kanały ze stron co pewien czas, by sprawdzić, czy na stronie pojawiło się coś nowego. Osobiście używam ich jako głównego sposobu na dowiadywanie się o nowościach ze światka webdevu.</p>
<p>Obecnie mamy trzy standardy kanałów: <a href="https://www.rssboard.org/rss-specification" rel="noreferrer noopener"><span lang="en">Really Simple Syndication</span> (RSS)</a>, <a href="https://www.ietf.org/rfc/rfc4287.txt" rel="noreferrer noopener">Atom</a> oraz <a href="https://www.jsonfeed.org/version/1.1/" rel="noreferrer noopener">JSON Feed</a>. Te dwa pierwsze to formaty oparte o XML, podczas gdy ostatni, jak sama nazwa sugeruje, o JSON. Na tym blogu stosuję format Atom.</p>
<h2 id="problemy-z-kanałem"><a class="header-anchor" href="https://blog.comandeer.pl/ale-kanal#problemy-z-kanałem">Problemy z kanałem</a></h2>
<p>Przy okazji <a href="https://blog.comandeer.pl/blog-2.0">odświeżenia bloga</a> dodałem też do nagłówków w postach linki odsyłające do konkretnych sekcji:</p>
<figure class="code" typeof="SoftwareSourceCode">
			<figcaption class="code__caption">
				<span class="code__title" property="programmingLanguage">HTML</span>
				
			</figcaption>
			<div class="code__code" translate="no" property="text">
				<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">&lt;</span><span style="color:#22863A;--shiki-dark:#85E89D">h2</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> id</span><span style="color:#24292E;--shiki-dark:#E1E4E8">=</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"odświeżone-bloczki"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">&gt; </span><span style="color:#6A737D;--shiki-dark:#6A737D">&lt;!-- 2 --&gt;</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">	&lt;</span><span style="color:#22863A;--shiki-dark:#85E89D">a</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> class</span><span style="color:#24292E;--shiki-dark:#E1E4E8">=</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"header-anchor"</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> href</span><span style="color:#24292E;--shiki-dark:#E1E4E8">=</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"#odświeżone-bloczki"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">&gt;Odświeżone bloczki&lt;/</span><span style="color:#22863A;--shiki-dark:#85E89D">a</span><span style="color:#24292E;--shiki-dark:#E1E4E8">&gt; </span><span style="color:#6A737D;--shiki-dark:#6A737D">&lt;!-- 1 --&gt;</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">&lt;/</span><span style="color:#22863A;--shiki-dark:#85E89D">h2</span><span style="color:#24292E;--shiki-dark:#E1E4E8">&gt;</span></span>
<span class="line"></span></code></pre>

			</div>
		</figure><p>Zawartość nagłówka jest otoczona linkiem (1), który odsyła do tego nagłówka przy pomocy <a href="https://www.kurshtml.edu.pl/html/do_etykiety,odsylacze.html" rel="noreferrer noopener">kotwicy</a> (2). Problem polega na tym, że atrybut <code>[href]</code> zawiera samą nazwę kotwicy, nie cały URL. A to sprawia, że gdy taki link zostanie wyciągnięty poza oryginalny kontekst (np. do czytnika kanałów), linki w nagłówkach przestają działać. I od dłuższego czasu odkładałem poprawienie tego na później, aż w końcu…</p>
<div class="note" role="note" aria-labelledby="note-61"><p class="note__label" id="note-61">Dygresja</p><div class="note__content"><p>Jeśli zastanawiasz się, czemu wybrałem taki sposób linkowania sekcji, polecam <a href="https://amberwilson.co.uk/blog/are-your-anchor-links-accessible/" rel="noreferrer noopener">artykuł Amber Wilson o dostępnych kotwicach</a>.</p></div></div>
<p>…aż w końcu zmieniłem też bloczki kodu i do problemu z nagłówkami doszedł kolejny:</p>
<figure class="figure"><a class="figure__link" href="/assets/images/ale-kanal/problem-672w.avif"><picture><source type="image/avif" srcset="/assets/images/ale-kanal/problem-440w.avif 440w, /assets/images/ale-kanal/problem-672w.avif 672w" sizes="90vw"><source type="image/webp" srcset="/assets/images/ale-kanal/problem-440w.webp 440w, /assets/images/ale-kanal/problem-672w.webp 672w" sizes="90vw"><source type="image/png" srcset="/assets/images/ale-kanal/problem-440w.png 440w, /assets/images/ale-kanal/problem-672w.png 672w" sizes="90vw"><img src="/assets/images/ale-kanal/problem-672w.png" width="672" height="377" style="aspect-ratio: 672 / 377" alt="Bloczek kodu HTML, nad którym znajduje się tekst &quot;HTML Kopiuj&quot;." loading="lazy" decoding="async"></picture></a><figcaption class="figure__caption"><p>Fragment wpisu <a href="https://blog.comandeer.pl/inkunabuly-madrosci">Inkunabuły mądrości</a> w czytniku kanałów</p></figcaption></figure>
<p>Z racji tego, że czytnik kanałów wycina potencjalnie niebezpieczne elementy, przycisk do kopiowania zawartości bloczków kodu zamienił się w zwykły tekst. Co więcej, nawet gdyby mój czytnik zostawił w tym miejscu przycisk, to i tak by on nie zadziałał, bo nie ma sposobu na dodanie JS-a do kanału Atom. Innymi słowy: kanał mojego bloga miał już dwa niedziałające elementy. Przyszła pora, by coś z tym zrobić!</p>
<h2 id="naprawa-html-a"><a class="header-anchor" href="https://blog.comandeer.pl/ale-kanal#naprawa-html-a">Naprawa HTML-a</a></h2>
<p>Postanowiłem dodać <a href="https://www.11ty.dev/docs/filters/" rel="noreferrer noopener">filtr w Eleventy</a>, który brałby treść posta i wycinał z niego wszystkie elementy, które nie działają w kanale. Problem polega jednak na tym, że w momencie stosowania filtru zawartość posta jest już&nbsp;sparsowana z Markdowna do HTML-a. A to oznacza, że muszę parsować HTML. W tym celu postanowiłem użyć <a href="https://www.npmjs.com/package/cheerio" rel="noreferrer noopener">biblioteki cheerio</a>, która dostarcza API podobnego do <a href="https://jquery.com/" rel="noreferrer noopener">jQuery</a>. Ostatecznie powstał następujący filtr:</p>
<figure class="code" typeof="SoftwareSourceCode">
			<figcaption class="code__caption">
				<span class="code__title" property="programmingLanguage">JavaScript</span>
				
			</figcaption>
			<div class="code__code" translate="no" property="text">
				<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">import</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> { load } </span><span style="color:#D73A49;--shiki-dark:#F97583">from</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> 'cheerio'</span><span style="color:#24292E;--shiki-dark:#E1E4E8">; </span><span style="color:#6A737D;--shiki-dark:#6A737D">// 2</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">const</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> MORE_COMMENT_REGEX</span><span style="color:#D73A49;--shiki-dark:#F97583"> =</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> /</span><span style="color:#032F62;--shiki-dark:#DBEDFF">&lt;!--</span><span style="color:#005CC5;--shiki-dark:#79B8FF">\s</span><span style="color:#D73A49;--shiki-dark:#F97583">*</span><span style="color:#032F62;--shiki-dark:#DBEDFF">more</span><span style="color:#005CC5;--shiki-dark:#79B8FF">\s</span><span style="color:#D73A49;--shiki-dark:#F97583">*</span><span style="color:#032F62;--shiki-dark:#DBEDFF">--&gt;</span><span style="color:#032F62;--shiki-dark:#9ECBFF">/</span><span style="color:#24292E;--shiki-dark:#E1E4E8">; </span><span style="color:#6A737D;--shiki-dark:#6A737D">// 10</span></span>
<span class="line"></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">eleventyConfig.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">addFilter</span><span style="color:#24292E;--shiki-dark:#E1E4E8">( </span><span style="color:#032F62;--shiki-dark:#9ECBFF">'rss_content'</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, ( </span><span style="color:#E36209;--shiki-dark:#FFAB70">post</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> ) </span><span style="color:#D73A49;--shiki-dark:#F97583">=&gt;</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> { </span><span style="color:#6A737D;--shiki-dark:#6A737D">// 1</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">	const</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> postURL</span><span style="color:#D73A49;--shiki-dark:#F97583"> =</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> cfUrl</span><span style="color:#24292E;--shiki-dark:#E1E4E8">( </span><span style="color:#D73A49;--shiki-dark:#F97583">new</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> URL</span><span style="color:#24292E;--shiki-dark:#E1E4E8">( post.data.permalink, post.data.site.url ).href ); </span><span style="color:#6A737D;--shiki-dark:#6A737D">// 7</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">	const</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> $</span><span style="color:#D73A49;--shiki-dark:#F97583"> =</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> load</span><span style="color:#24292E;--shiki-dark:#E1E4E8">( post.content ); </span><span style="color:#6A737D;--shiki-dark:#6A737D">// 3</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">	$</span><span style="color:#24292E;--shiki-dark:#E1E4E8">( </span><span style="color:#032F62;--shiki-dark:#9ECBFF">':where(h1, h2, h3, h4, h5, h6) a'</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> ).</span><span style="color:#6F42C1;--shiki-dark:#B392F0">each</span><span style="color:#24292E;--shiki-dark:#E1E4E8">( ( </span><span style="color:#E36209;--shiki-dark:#FFAB70">_</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, </span><span style="color:#E36209;--shiki-dark:#FFAB70">link</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> ) </span><span style="color:#D73A49;--shiki-dark:#F97583">=&gt;</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> { </span><span style="color:#6A737D;--shiki-dark:#6A737D">// 4</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">		const</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> $link</span><span style="color:#D73A49;--shiki-dark:#F97583"> =</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> $</span><span style="color:#24292E;--shiki-dark:#E1E4E8">( link );</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">		const</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> originalHref</span><span style="color:#D73A49;--shiki-dark:#F97583"> =</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> $link.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">attr</span><span style="color:#24292E;--shiki-dark:#E1E4E8">( </span><span style="color:#032F62;--shiki-dark:#9ECBFF">'href'</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> ); </span><span style="color:#6A737D;--shiki-dark:#6A737D">// 5</span></span>
<span class="line"></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">		$link.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">attr</span><span style="color:#24292E;--shiki-dark:#E1E4E8">( </span><span style="color:#032F62;--shiki-dark:#9ECBFF">'href'</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, postURL </span><span style="color:#D73A49;--shiki-dark:#F97583">+</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> originalHref ); </span><span style="color:#6A737D;--shiki-dark:#6A737D">// 6</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">	} );</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">	$</span><span style="color:#24292E;--shiki-dark:#E1E4E8">( </span><span style="color:#032F62;--shiki-dark:#9ECBFF">'.code__copy'</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> ).</span><span style="color:#6F42C1;--shiki-dark:#B392F0">remove</span><span style="color:#24292E;--shiki-dark:#E1E4E8">();</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">	return</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> $</span><span style="color:#24292E;--shiki-dark:#E1E4E8">( </span><span style="color:#032F62;--shiki-dark:#9ECBFF">'body'</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> ).</span><span style="color:#6F42C1;--shiki-dark:#B392F0">html</span><span style="color:#24292E;--shiki-dark:#E1E4E8">().</span><span style="color:#6F42C1;--shiki-dark:#B392F0">replace</span><span style="color:#24292E;--shiki-dark:#E1E4E8">( </span><span style="color:#005CC5;--shiki-dark:#79B8FF">MORE_COMMENT_REGEX</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, </span><span style="color:#032F62;--shiki-dark:#9ECBFF">''</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> ); </span><span style="color:#6A737D;--shiki-dark:#6A737D">// 9</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">} );</span></span>
<span class="line"></span></code></pre>

			</div>
		</figure><p>Dodajemy nowy filtr o nazwie <code>rss_content</code> (1). Przekazujemy mu <a href="https://www.11ty.dev/docs/data-eleventy-supplied/#page-variable" rel="noreferrer noopener">obiekt wpisu</a>. Następnie importujemy <a href="https://cheerio.js.org/docs/api/variables/load" rel="noreferrer noopener">funkcję <code>load()</code></a> z biblioteki <code>cheerio</code> (2). Pozwala ona sparsować kod HTML do DOM-u. Używamy jej do sparsowania zawartości postu (3). Na tak stworzonym drzewku wyszukujemy wszystkie linki w naglówkach (4) i dla każdego z nich pobieramy wartość atrybutu <code>[href]</code> (5) oraz zamieniamy go na absolutny URL (6) – a więc <code>#odświeżone-bloczki</code> zamienią się na <code>https://blog.comandeer.pl/inkunabuly-madrosci#odświeżone-bloczki</code>. Absolutny URL uzyskujemy, sklejając link do posta z linkiem do strony przy pomocy <a href="https://developer.mozilla.org/en-US/docs/Web/API/URL/URL" rel="noreferrer noopener">konstruktora <code>URL</code></a> (7). Link do posta jest dostarczony przez Eleventy, natomiast link do strony – <a href="https://github.com/Comandeer/blog/blob/7b5582136be29b149646515c5b355ccb2b6c6b7d/src/_data/site.js#L3" rel="noreferrer noopener">przeze mnie</a> przy pomocy mechanizmu <a href="https://www.11ty.dev/docs/data-global/" rel="noreferrer noopener">globalnych plików z danymi</a>. Po naprawieniu linków, wyszukujemy i <a href="https://cheerio.js.org/docs/api/classes/Cheerio#remove" rel="noreferrer noopener">usuwamy</a> wszystkie przyciski do kopiowania kodu (8). Na koniec <a href="https://cheerio.js.org/docs/api/classes/Cheerio#html" rel="noreferrer noopener">wyciągamy kod HTML</a> z DOM-u i usuwamy z niego komentarz <code>&lt;!--more--&gt;</code> (9) przy pomocy wyrażenia regularnego (10). Mimo usilnych starań nie udało mi się usunąć go w bardziej przyzwoity sposób, używając API biblioteki cheerio. Warto tutaj zwrócić uwagę, że kod HTML wyciągamy z elementu <code>body</code>. Biblioteka cheerio parsuje bowiem HTML zgodnie z regułami opisanymi w specyfikacji, a więc – <a href="https://html.spec.whatwg.org/multipage/dynamic-markup-insertion.html#dom-domparser-parsefromstring" rel="noreferrer noopener">jako pełny dokument</a>, co sprawia, że dodaje brakujące elementy <code>html</code> i <code>body</code>. Można też wymusić na cheerio <a href="https://html.spec.whatwg.org/multipage/dynamic-markup-insertion.html#fragment-parsing-algorithm-steps" rel="noreferrer noopener">parsowanie w trybie fragmentu</a>, podając <code>false</code> jako trzeci argument do funkcji <code>load()</code>.</p>
<div class="note" role="note" aria-labelledby="note-62"><p class="note__label" id="note-62">Dygresja</p><div class="note__content"><p>Komentarz <code>&lt;!--more--&gt;</code> to technika, którą ukradłem z… <a href="https://codex.wordpress.org/Customizing_the_Read_More" rel="noreferrer noopener">WordPressa</a>. Służy on do oddzielania wstępu artykułu od reszty treści. Wstęp jest widoczny na stronie głównej bloga wraz z linkiem <q>Czytaj więcej</q>, prowadzącym do podstrony z już pełną wersją artykułu.</p></div></div>
<p>Tajemnicza funkcja <code>cfUrl()</code> z poprzedniego fragmentu kodu wycina z przekazanego linku rozszerzenie <code>.html</code> lub fragment <code>index.html</code>:</p>
<figure class="code" typeof="SoftwareSourceCode">
			<figcaption class="code__caption">
				<span class="code__title" property="programmingLanguage">JavaScript</span>
				
			</figcaption>
			<div class="code__code" translate="no" property="text">
				<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">function</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> cfUrl</span><span style="color:#24292E;--shiki-dark:#E1E4E8">( </span><span style="color:#E36209;--shiki-dark:#FFAB70">url</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> ) {</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">	return</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> url.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">replace</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> /</span><span style="color:#032F62;--shiki-dark:#DBEDFF">index</span><span style="color:#22863A;--shiki-light-font-weight:bold;--shiki-dark:#85E89D;--shiki-dark-font-weight:bold">\.</span><span style="color:#032F62;--shiki-dark:#DBEDFF">html</span><span style="color:#D73A49;--shiki-dark:#F97583">$</span><span style="color:#032F62;--shiki-dark:#9ECBFF">/</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, </span><span style="color:#032F62;--shiki-dark:#9ECBFF">''</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> ).</span><span style="color:#6F42C1;--shiki-dark:#B392F0">replace</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> /</span><span style="color:#22863A;--shiki-light-font-weight:bold;--shiki-dark:#85E89D;--shiki-dark-font-weight:bold">\.</span><span style="color:#032F62;--shiki-dark:#DBEDFF">html</span><span style="color:#D73A49;--shiki-dark:#F97583">$</span><span style="color:#032F62;--shiki-dark:#9ECBFF">/</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, </span><span style="color:#032F62;--shiki-dark:#9ECBFF">''</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> );</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">}</span></span>
<span class="line"></span></code></pre>

			</div>
		</figure><p>Dzięki temu linki typu <code>/blog-2.0.html</code> lub <code>/kategorie/javascript/index.html</code> zmienią się odpowiednio w <code>/blog-2.0</code> oraz <code>/kategorie/javascript</code>.</p>
<p>Tak stworzony filtr dodajemy następnie do szablonu kanałów, w miejscu, w którym wyświetlana jest treść posta:</p>
<figure class="code" typeof="SoftwareSourceCode">
			<figcaption class="code__caption">
				<span class="code__title" property="programmingLanguage">Liquid</span>
				
			</figcaption>
			<div class="code__code" translate="no" property="text">
				<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">&lt;</span><span style="color:#B31D28;--shiki-light-font-style:italic;--shiki-dark:#FDAEB7;--shiki-dark-font-style:italic">content</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> type</span><span style="color:#24292E;--shiki-dark:#E1E4E8">=</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"html"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">&gt;&lt;![CDATA[</span><span style="color:#032F62;--shiki-dark:#9ECBFF">{{ post | rss_content }}</span><span style="color:#24292E;--shiki-dark:#E1E4E8">]]&gt;&lt;/</span><span style="color:#B31D28;--shiki-light-font-style:italic;--shiki-dark:#FDAEB7;--shiki-dark-font-style:italic">content</span><span style="color:#24292E;--shiki-dark:#E1E4E8">&gt;</span></span>
<span class="line"></span></code></pre>

			</div>
		</figure><p>I to w sumie tyle! W końcu kanały bloga powinny działać poprawnie.</p>
]]></content>
		</entry>
	
		<entry>
			<title type="html">Inkunabuły mądrości</title>
			
				<author>
					<name>Comandeer</name>
				</author>
			
			<link href="https://blog.comandeer.pl/inkunabuly-madrosci" rel="alternate" type="text/html"/>
			<published>2025-12-20T23:00:00.000Z</published>
			<updated>2025-12-20T23:00:00.000Z</updated>
			<id>https://blog.comandeer.pl/inkunabuly-madrosci</id>
			
				<summary><![CDATA[Poprawiłem bloczki kodu!]]></summary>
			
			<content type="html"><![CDATA[<p>Po odświeżeniu dygresji przyszedł czas na kolejny częsty element mojego bloga: bloczki z kodem!</p>
<h2 id="stare-bloczki"><a class="header-anchor" href="https://blog.comandeer.pl/inkunabuly-madrosci#stare-bloczki">Stare bloczki</a></h2>
<p>Stare bloczki… były:</p>
<figure class="figure"><a class="figure__link" href="/assets/images/inkunabuly-madrosci/stary-bloczek-725w.avif"><picture><source type="image/avif" srcset="/assets/images/inkunabuly-madrosci/stary-bloczek-440w.avif 440w, /assets/images/inkunabuly-madrosci/stary-bloczek-725w.avif 725w" sizes="90vw"><source type="image/webp" srcset="/assets/images/inkunabuly-madrosci/stary-bloczek-440w.webp 440w, /assets/images/inkunabuly-madrosci/stary-bloczek-725w.webp 725w" sizes="90vw"><source type="image/png" srcset="/assets/images/inkunabuly-madrosci/stary-bloczek-440w.png 440w, /assets/images/inkunabuly-madrosci/stary-bloczek-725w.png 725w" sizes="90vw"><img src="/assets/images/inkunabuly-madrosci/stary-bloczek-725w.png" width="725" height="173" style="aspect-ratio: 725 / 173" alt="Bloczek kodu CSS z ciemnego motywu bloga, otoczony białym obramowaniem." loading="lazy" decoding="async"></picture></a><figcaption class="figure__caption"><p>Przykład bloczka kodu z wpisu <a href="https://blog.comandeer.pl/frywolne-marginalia">Frywolne marginalia</a></p></figcaption></figure>
<p>Co prawda spełniały swoje zadanie, miały nawet dobre kolorowanie składni (dzięki <a href="https://shiki.matsu.io/" rel="noreferrer noopener">Shiki</a>), ale brakowało w nich choćby informacji o języku programowania. Dlatego też postanowiłem je nieco odświeżyc.</p>
<h2 id="odświeżone-bloczki"><a class="header-anchor" href="https://blog.comandeer.pl/inkunabuly-madrosci#odświeżone-bloczki">Odświeżone bloczki</a></h2>
<p>Nowe bloczki wyglądają następująco:</p>
<figure class="figure"><a class="figure__link" href="/assets/images/inkunabuly-madrosci/nowy-bloczek-643w.avif"><picture><source type="image/avif" srcset="/assets/images/inkunabuly-madrosci/nowy-bloczek-440w.avif 440w, /assets/images/inkunabuly-madrosci/nowy-bloczek-643w.avif 643w" sizes="90vw"><source type="image/webp" srcset="/assets/images/inkunabuly-madrosci/nowy-bloczek-440w.webp 440w, /assets/images/inkunabuly-madrosci/nowy-bloczek-643w.webp 643w" sizes="90vw"><source type="image/png" srcset="/assets/images/inkunabuly-madrosci/nowy-bloczek-440w.png 440w, /assets/images/inkunabuly-madrosci/nowy-bloczek-643w.png 643w" sizes="90vw"><img src="/assets/images/inkunabuly-madrosci/nowy-bloczek-643w.png" width="643" height="209" style="aspect-ratio: 643 / 209" alt="Bloczek kodu CSS z ciemnego motywu bloga, nad którym znajduje się pasek z nazwą języka programowania oraz przyciskiem &quot;Kopiuj&quot;" loading="lazy" decoding="async"></picture></a><figcaption class="figure__caption"><p>Przykład odświezonego bloczka kodu z wpisu <a href="https://blog.comandeer.pl/frywolne-marginalia">Frywolne marginalia</a></p></figcaption></figure>
<p>Tak naprawdę zmiany nie są szczególnie wielkie – pojawiła się jedynie nazwa języka oraz przycisk do skopiowania kodu do schowka.</p>
<h3 id="nowy-html"><a class="header-anchor" href="https://blog.comandeer.pl/inkunabuly-madrosci#nowy-html">Nowy HTML</a></h3>
<p>Od strony HTML-a cały bloczek kodu trafił do elementu <code>figure</code>:</p>
<figure class="code" typeof="SoftwareSourceCode">
			<figcaption class="code__caption">
				<span class="code__title" property="programmingLanguage">HTML</span>
				
			</figcaption>
			<div class="code__code" translate="no" property="text">
				<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">&lt;</span><span style="color:#22863A;--shiki-dark:#85E89D">figure</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> class</span><span style="color:#24292E;--shiki-dark:#E1E4E8">=</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"code"</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> typeof</span><span style="color:#24292E;--shiki-dark:#E1E4E8">=</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"SoftwareSourceCode"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">&gt;</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">	&lt;</span><span style="color:#22863A;--shiki-dark:#85E89D">figcaption</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> class</span><span style="color:#24292E;--shiki-dark:#E1E4E8">=</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"code__caption"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">&gt; </span><span style="color:#6A737D;--shiki-dark:#6A737D">&lt;!-- 3 --&gt;</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">		&lt;</span><span style="color:#22863A;--shiki-dark:#85E89D">span</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> class</span><span style="color:#24292E;--shiki-dark:#E1E4E8">=</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"code__title"</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> property</span><span style="color:#24292E;--shiki-dark:#E1E4E8">=</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"programmingLanguage"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">&gt;CSS&lt;/</span><span style="color:#22863A;--shiki-dark:#85E89D">span</span><span style="color:#24292E;--shiki-dark:#E1E4E8">&gt; </span><span style="color:#6A737D;--shiki-dark:#6A737D">&lt;!-- 1 --&gt;</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">		&lt;</span><span style="color:#22863A;--shiki-dark:#85E89D">button</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> class</span><span style="color:#24292E;--shiki-dark:#E1E4E8">=</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"code__copy"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">&gt;Kopiuj&lt;/</span><span style="color:#22863A;--shiki-dark:#85E89D">button</span><span style="color:#24292E;--shiki-dark:#E1E4E8">&gt; </span><span style="color:#6A737D;--shiki-dark:#6A737D">&lt;!-- 2 --&gt;</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">	&lt;/</span><span style="color:#22863A;--shiki-dark:#85E89D">figcaption</span><span style="color:#24292E;--shiki-dark:#E1E4E8">&gt;</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">	&lt;</span><span style="color:#22863A;--shiki-dark:#85E89D">div</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> class</span><span style="color:#24292E;--shiki-dark:#E1E4E8">=</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"code__code"</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> translate</span><span style="color:#24292E;--shiki-dark:#E1E4E8">=</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"no"</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> property</span><span style="color:#24292E;--shiki-dark:#E1E4E8">=</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"text"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">&gt; </span><span style="color:#6A737D;--shiki-dark:#6A737D">&lt;!-- 4 --&gt;</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">		&lt;</span><span style="color:#22863A;--shiki-dark:#85E89D">pre</span><span style="color:#24292E;--shiki-dark:#E1E4E8">&gt;&lt;</span><span style="color:#22863A;--shiki-dark:#85E89D">code</span><span style="color:#24292E;--shiki-dark:#E1E4E8">&gt;[…]&lt;/</span><span style="color:#22863A;--shiki-dark:#85E89D">code</span><span style="color:#24292E;--shiki-dark:#E1E4E8">&gt;&lt;/</span><span style="color:#22863A;--shiki-dark:#85E89D">pre</span><span style="color:#24292E;--shiki-dark:#E1E4E8">&gt; </span><span style="color:#6A737D;--shiki-dark:#6A737D">&lt;!-- 5 --&gt;</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">	&lt;/</span><span style="color:#22863A;--shiki-dark:#85E89D">div</span><span style="color:#24292E;--shiki-dark:#E1E4E8">&gt;</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">&lt;/</span><span style="color:#22863A;--shiki-dark:#85E89D">figure</span><span style="color:#24292E;--shiki-dark:#E1E4E8">&gt;</span></span>
<span class="line"></span>
<span class="line"></span></code></pre>

			</div>
		</figure><p>Podpis z nazwą języka (1) i przyciskiem (2) został umieszczony w <code>figcaption</code> (3), natomiast sam kod w dodatkowym <code>div</code>ie z <a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Global_attributes/translate" rel="noreferrer noopener">atrybutem <code>[translate=no]</code></a> (4). Dzięki temu Google Translate i podobne usługi nie będą próbowały tłumaczyć kodu na inne języki. Dodatkowo zastosowałem też <a href="https://blog.comandeer.pl/o-semantyce-slow-kilka#rozszerzanie-semantyki">RDFa</a>, żeby oznaczyć całość jako kod w języku programowania. Sam kod (5) pozostał bez zmian – dalej jest kolorowany przez Shiki.</p>
<h3 id="nowy-js"><a class="header-anchor" href="https://blog.comandeer.pl/inkunabuly-madrosci#nowy-js">Nowy JS</a></h3>
<p>Przycisk do kopiowania kodu potrzebuje obsługi w JS-ie. Dodałem więc taką do swojego pliku JS:</p>
<figure class="code" typeof="SoftwareSourceCode">
			<figcaption class="code__caption">
				<span class="code__title" property="programmingLanguage">JavaScript</span>
				
			</figcaption>
			<div class="code__code" translate="no" property="text">
				<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">document.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">addEventListener</span><span style="color:#24292E;--shiki-dark:#E1E4E8">( </span><span style="color:#032F62;--shiki-dark:#9ECBFF">'click'</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, </span><span style="color:#D73A49;--shiki-dark:#F97583">async</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> ( </span><span style="color:#E36209;--shiki-dark:#FFAB70">evt</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> ) </span><span style="color:#D73A49;--shiki-dark:#F97583">=&gt;</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> { </span><span style="color:#6A737D;--shiki-dark:#6A737D">// 1</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">	const</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> isCopyButton</span><span style="color:#D73A49;--shiki-dark:#F97583"> =</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> evt.target.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">closest</span><span style="color:#24292E;--shiki-dark:#E1E4E8">( </span><span style="color:#032F62;--shiki-dark:#9ECBFF">'.code__copy'</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> ); </span><span style="color:#6A737D;--shiki-dark:#6A737D">// 2</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">	if</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> ( </span><span style="color:#D73A49;--shiki-dark:#F97583">!</span><span style="color:#24292E;--shiki-dark:#E1E4E8">isCopyButton ) {</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">		return</span><span style="color:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">	}</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">	const</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> closestCode</span><span style="color:#D73A49;--shiki-dark:#F97583"> =</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> evt.target.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">closest</span><span style="color:#24292E;--shiki-dark:#E1E4E8">( </span><span style="color:#032F62;--shiki-dark:#9ECBFF">'.code'</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> ).</span><span style="color:#6F42C1;--shiki-dark:#B392F0">querySelector</span><span style="color:#24292E;--shiki-dark:#E1E4E8">( </span><span style="color:#032F62;--shiki-dark:#9ECBFF">'.code__code'</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> ); </span><span style="color:#6A737D;--shiki-dark:#6A737D">// 3</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">	await</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> navigator.clipboard.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">writeText</span><span style="color:#24292E;--shiki-dark:#E1E4E8">( closestCode.innerText ); </span><span style="color:#6A737D;--shiki-dark:#6A737D">// 4</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">} );</span></span>
<span class="line"></span></code></pre>

			</div>
		</figure><p>Stosuje tutaj technikę <a href="https://javascript.info/event-delegation" rel="noreferrer noopener"><i lang="en">event delegation</i> (delegacji zdarzeń)</a> (1). Dzięki temu mogę przypiąć tylko jeden nasłuchiwacz i obsłużyć wszystkie przyciski “Kopiuj”, zamiast musieć się przypiąć do każdego z nich osobno. Niemniej ta technika sprawia, ze muszę sprawdzić, czy na pewno został kliknięty przycisk “Kopiuj”. Robię to przy pomocy wyszukania go przez <a href="https://developer.mozilla.org/en-US/docs/Web/API/Element/closest" rel="noreferrer noopener">metodę <code>#closest()</code></a> (2). Metoda ta zwróci najbliższy element o klasie <code>.code__copy</code>. W przypadku, gdy ktoś kliknie przycisk, będzie to on sam. Metoda <code>#closest()</code> zabezpiecza nas przed przypadkiem, gdybyśmy dodali kiedyś ikonkę do przycisku i ktoś kliknął w nią – wówczas zamiast przycisku mielibyśmy w <code>evt.target</code> np. element <code>svg</code>. Jak już ustalimy, że został naciśnięty odpowiedni przycisk, wyszukujemy najbliższy bloczek kodu (3). Następnie wywołujemy <a href="https://developer.mozilla.org/en-US/docs/Web/API/Clipboard/writeText" rel="noreferrer noopener">metodę <code>Clipboard#writeText()</code></a>, do której przekazujemy tekstową zawartość bloczka (4). Jeśli osoba naciskająca przycisk wyrazi zgodę na zapisanie danych do schowka, kod zostanie skopiowany.</p>
<h3 id="nowy-backend"><a class="header-anchor" href="https://blog.comandeer.pl/inkunabuly-madrosci#nowy-backend">Nowy backend</a></h3>
<p>Nowe bloczki kodu renderowane są przy pomocy autorskiego pluginu do <a href="https://www.npmjs.com/package/markdown-it" rel="noreferrer noopener">MarkdownIt</a> (biblioteki renderującej Markdowna w Eleventy). Jego kod prezentuje się następująco:</p>
<figure class="code" typeof="SoftwareSourceCode">
			<figcaption class="code__caption">
				<span class="code__title" property="programmingLanguage">JavaScript</span>
				
			</figcaption>
			<div class="code__code" translate="no" property="text">
				<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">function</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> markdownItCodeBlock</span><span style="color:#24292E;--shiki-dark:#E1E4E8">( </span><span style="color:#E36209;--shiki-dark:#FFAB70">markdownIt</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> ) {</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">	const</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> originalFenceRule</span><span style="color:#D73A49;--shiki-dark:#F97583"> =</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> markdownIt.renderer.rules.fence; </span><span style="color:#6A737D;--shiki-dark:#6A737D">// 1</span></span>
<span class="line"></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">	markdownIt.renderer.rules.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">fence</span><span style="color:#D73A49;--shiki-dark:#F97583"> =</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> ( </span><span style="color:#E36209;--shiki-dark:#FFAB70">tokens</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, </span><span style="color:#E36209;--shiki-dark:#FFAB70">idx</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, </span><span style="color:#E36209;--shiki-dark:#FFAB70">options</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, </span><span style="color:#E36209;--shiki-dark:#FFAB70">env</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, </span><span style="color:#E36209;--shiki-dark:#FFAB70">slf</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> ) </span><span style="color:#D73A49;--shiki-dark:#F97583">=&gt;</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> {</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">		const</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> token</span><span style="color:#D73A49;--shiki-dark:#F97583"> =</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> tokens[ idx ];</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">		const</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> lang</span><span style="color:#D73A49;--shiki-dark:#F97583"> =</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> token.info </span><span style="color:#D73A49;--shiki-dark:#F97583">?</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> token.info.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">trim</span><span style="color:#24292E;--shiki-dark:#E1E4E8">() </span><span style="color:#D73A49;--shiki-dark:#F97583">:</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> ''</span><span style="color:#24292E;--shiki-dark:#E1E4E8">; </span><span style="color:#6A737D;--shiki-dark:#6A737D">// 3</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">		const</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> renderedCodeBlock</span><span style="color:#D73A49;--shiki-dark:#F97583"> =</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> originalFenceRule</span><span style="color:#24292E;--shiki-dark:#E1E4E8">( tokens, idx, options, env, slf ); </span><span style="color:#6A737D;--shiki-dark:#6A737D">// 4</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">		/</span><span style="color:#D73A49;--shiki-dark:#F97583"> *</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> 5</span><span style="color:#D73A49;--shiki-dark:#F97583"> */</span><span style="color:#D73A49;--shiki-dark:#F97583"> return</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> `&lt;figure class="code" typeof="SoftwareSourceCode"&gt;</span></span>
<span class="line"><span style="color:#032F62;--shiki-dark:#9ECBFF">			&lt;figcaption class="code__caption"&gt;</span></span>
<span class="line"><span style="color:#032F62;--shiki-dark:#9ECBFF">				&lt;span class="code__title" property="programmingLanguage"&gt;${</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> langs</span><span style="color:#032F62;--shiki-dark:#9ECBFF">[ </span><span style="color:#24292E;--shiki-dark:#E1E4E8">lang</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> ] </span><span style="color:#D73A49;--shiki-dark:#F97583">??</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> ''</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> }&lt;/span&gt;</span></span>
<span class="line"><span style="color:#032F62;--shiki-dark:#9ECBFF">				&lt;button class="code__copy"&gt;Kopiuj&lt;/button&gt;</span></span>
<span class="line"><span style="color:#032F62;--shiki-dark:#9ECBFF">			&lt;/figcaption&gt;</span></span>
<span class="line"><span style="color:#032F62;--shiki-dark:#9ECBFF">			&lt;div class="code__code" translate="no" property="text"&gt;</span></span>
<span class="line"><span style="color:#032F62;--shiki-dark:#9ECBFF">				${</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> renderedCodeBlock</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> }</span></span>
<span class="line"><span style="color:#032F62;--shiki-dark:#9ECBFF">			&lt;/div&gt;</span></span>
<span class="line"><span style="color:#032F62;--shiki-dark:#9ECBFF">		&lt;/figure&gt;`</span><span style="color:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">	};</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">}</span></span>
<span class="line"></span></code></pre>

			</div>
		</figure><p>Na początku zapisujemy sobie do zmiennej aktualną funkcję do renderowania bloczków kodu (1). W przypadku MarkdownIt funkcja ta znajduje się w <a href="https://markdown-it.github.io/markdown-it/#Renderer.prototype.rules" rel="noreferrer noopener">regułach renderera</a>. Następnie nadpisujemy tę regułę własną (2). Wyciągamy z kodu Markdown nazwę języka programowania (3), a następnie wywołujemy oryginalną regułę renderującą bloczki kodu (4). Dzięki temu dostajemy kod ładnie pokolorowany przez Shiki. Na koniec  tworzymy nasz własny kod HTML bloczka (5), do którego wsadzamy pokolorowany kod i zwracamy całość&nbsp;z funkcji.</p>
<p>Teraz zostaje tylko dodać ten plugin do <a href="https://github.com/Comandeer/blog/blob/983e2db060f90d4bcfc923187c2147d3c937e12d/plugins/markdownIt.js#L113" rel="noreferrer noopener">naszej zmodyfikowanej wersji MarkdownIt</a>. Można to zrobić przy pomocy <a href="https://markdown-it.github.io/markdown-it/#MarkdownIt.use" rel="noreferrer noopener">metody <code>#use()</code></a>.</p>
<p>I to tyle! Bloczki kodu na blogu są od teraz <em>nieco</em> lepsze.</p>
]]></content>
		</entry>
	
		<entry>
			<title type="html">Równając w dół</title>
			
				<author>
					<name>Comandeer</name>
				</author>
			
			<link href="https://blog.comandeer.pl/rownajac-w-dol" rel="alternate" type="text/html"/>
			<published>2025-12-20T16:11:00.000Z</published>
			<updated>2025-12-20T16:11:00.000Z</updated>
			<id>https://blog.comandeer.pl/rownajac-w-dol</id>
			
				<summary><![CDATA[Kompatybilność międzyprzeglądarkowa to spory problem. Baseline ma pomóc go rozwiązać]]></summary>
			
			<content type="html"><![CDATA[<p>Kompatybilność międzyprzeglądarkowa od lat jest <a href="https://2025.stateofhtml.com/en-US/usage/" rel="noreferrer noopener">jednym z największych problemów webdevu</a>. Dlatego powstał Baseline, który ma pomóc ten problem rozwiązać.</p>
<h2 id="kompatybilność"><a class="header-anchor" href="https://blog.comandeer.pl/rownajac-w-dol#kompatybilność">Kompatybilność?</a></h2>
<p>Obecnie na rynku mamy trzy duże silniki przeglądarek: <a href="https://en.wikipedia.org/wiki/Blink_(browser_engine)" rel="noreferrer noopener">Blink</a> (używany w Chrome), <a href="https://en.wikipedia.org/wiki/WebKit" rel="noreferrer noopener">WebKit</a> (Safari) oraz <a href="https://en.wikipedia.org/wiki/Gecko_(software)" rel="noreferrer noopener">Gecko</a> (Firefox). Do tego dochodzą różne przeglądarki oparte na różnych wersjach tych trzech silników, ale też np. rozwiązania pokroju <a href="https://en.wikipedia.org/wiki/Electron_(software_framework)" rel="noreferrer noopener">Electrona</a>, który pozwala tworzyć aplikacje desktopowe na Chromium. Powstają&nbsp;też dwa nowe, duże silniki: <a href="https://en.wikipedia.org/wiki/Servo_(software)" rel="noreferrer noopener">Servo</a> oraz <a href="https://en.wikipedia.org/wiki/Ladybird_(web_browser)" rel="noreferrer noopener">Ladybird</a>.</p>
<p>W teorii wszystkie te silniki wspierają&nbsp;te same standardy sieciowe. Ale w praktyce różnice mogą być znaczące. Po pierwsze, istnieje bardzo dużo standardów sieciowych, które nieustannie się zmieniają (np. <a href="https://html.spec.whatwg.org/multipage/" rel="noreferrer noopener">specyfikacja HTML</a> w momencie pisania tego artykułu była zmieniana ostatnio 17 grudnia 2025). Niektóre przeglądarki nie mają na tyle zasobów, by implementować wszystkie najnowsze standardy i robią&nbsp;to z opóźnieniem. Dodatkowo, nawet jeśli przeglądarka X zaimplementuje jakiś standard z wersji z listopada, to wersja z grudnia może być już&nbsp;zupełnie inna. Tym samym dwie przeglądarki implementujące ten sam standard mogą nie być kompatybilne między sobą.</p>
<p>Kolejnym czynnikiem są różne priorytety przeglądarek. Niektóre przeglądarki skupiają się na dostarczaniu coraz to nowych ficzerów, podczas gdy inne stawiają na innowacje w innych obszarach, jak np. UI samej przeglądarki. Dobrym przykładem może być tutaj <a href="https://www.chromium.org/teams/web-capabilities-fugu/" rel="noreferrer noopener">Projekt Fugu</a>. Google zgłosiło szereg propozycji standardów sieciowych, które miały zminimalizować różnice między możliwościami aplikacji natywnych oraz webowych. Wśród propozycji były <a href="http://m.in" rel="noreferrer noopener">m.in</a>. <a href="https://developer.mozilla.org/en-US/docs/Web/API/WebUSB_API" rel="noreferrer noopener">WebUSB</a> czy <a href="https://developer.mozilla.org/en-US/docs/Web/API/Web_NFC_API" rel="noreferrer noopener">Web NFC</a>. Zarówno <a href="https://mozilla.github.io/standards-positions/#webusb" rel="noreferrer noopener">Firefox</a>, jak i <a href="https://webkit.org/standards-positions/#position-68" rel="noreferrer noopener">Safari</a>, odmówiły implementacji tych propozycji, wskazując na potencjalne problemy z bezpieczeństwem i prywatnością.</p>
<p>Wreszcie: zarówno przeglądarki, jak i same specyfikacje, mają bugi. Dodatkowo specyfikacje można interpretować na różne sposoby, nawet jeśli włożono spory wysiłek w to, by ich tekst był jak najbardziej jednoznaczny. Tutaj na pomoc przychodzą <a href="https://wpt.fyi/results/" rel="noreferrer noopener">Web Platform Tests</a> – testy sprawdzające zgodność implementacji ze standardami sieciowymi. I choć dostarczają one dokładnych wyników, to są przeznaczone raczej dla osób, które zawodowo zajmują się tworzeniem przeglądarek lub standardów. Dla całej reszty mogą być mało czytelne.</p>
<h2 id="baseline"><a class="header-anchor" href="https://blog.comandeer.pl/rownajac-w-dol#baseline">Baseline</a></h2>
<p>Dlatego też powstał <a href="https://developer.mozilla.org/en-US/docs/Glossary/Baseline/Compatibility" rel="noreferrer noopener">Baseline</a>. To wskaźnik <a href="https://web.dev/baseline/overview" rel="noreferrer noopener">pierwotnie zaproponowany przez Chrome’a w trakcie Google I/O 2023</a>. Obecnie jest rozwijany przez <a href="https://www.w3.org/community/webdx/" rel="noreferrer noopener">Web Developer Experience Community Group</a>. Jego zadaniem jest wskazywanie, czy dany ficzer jest już dostępny we wszystkich najpopularniejszych przeglądarkach, czyli w Chrome (na desktopie i Androdzie), Firefoksie (na desktopie i Androidzie), Edge’u (na desktopie) oraz Safari (na desktopie i iOS-ie).</p>
<p>Każdy ficzer może mieć trzy poziomy wsparcia:</p>
<ul>
<li><picture style="display: inline-block;block-size: 2rem;inline-size: 2rem;"><source type="image/svg+xml" srcset="/assets/images/rownajac-w-dol/limited-availability-540w.svg 540w" sizes="90vw"><img src="/assets/images/rownajac-w-dol/limited-availability-540w.svg" width="540" height="300" style="aspect-ratio: 540 / 300" alt="" loading="lazy" decoding="async"></picture> <b lang="en">Limited availability</b> (ograniczona dostępność) – dany ficzer wciąż nie jest dostępny we wszystkich przeglądarkach,</li>
<li><picture style="display: inline-block;block-size: 2rem;inline-size: 2rem;"><source type="image/svg+xml" srcset="/assets/images/rownajac-w-dol/newly-available-540w.svg 540w" sizes="90vw"><img src="/assets/images/rownajac-w-dol/newly-available-540w.svg" width="540" height="300" style="aspect-ratio: 540 / 300" alt="" loading="lazy" decoding="async"></picture> <b lang="en">Newly available</b> (świeżo dostępny) – dany ficzer jest dostępny w najnowszych wersjach przeglądarek, może nie działać w starszych,</li>
<li><picture style="display: inline-block;block-size: 2rem;inline-size: 2rem;"><source type="image/svg+xml" srcset="/assets/images/rownajac-w-dol/widely-available-540w.svg 540w" sizes="90vw"><img src="/assets/images/rownajac-w-dol/widely-available-540w.svg" width="540" height="300" style="aspect-ratio: 540 / 300" alt="" loading="lazy" decoding="async"></picture> <b lang="en">Widely available</b> (szeroko dostępny) – dany ficzer jest dostępny we wszystkich przeglądarkach od co najmniej 30 miesięcy (2.5 roku).</li>
</ul>
<p>Baseline ma dostarczać prostej odpowiedzi na pytanie, czy ficzer X można już używać w projektach. Niemniej nie mówi nic ponad to, w jakich przeglądarkach dany ficzer jest dostępny. Jeśli potrzebujemy większej liczby szczegółów, wciąż trzeba sięgnąć po dokładniejsze źródła, takie jak choćby wspomniane już Web Platform Tests. Co więcej, wskaźnik ten nie uwzględnia różnic w implementacji danego standardu w przeglądarkach. Jedynie wskazuje, czy jest on obsługiwany w <em>jakikolwiek</em> sposób. A to może też prowadzić do przekłamań, np. niektóre API są oznaczone jako mające ograniczoną dostępność, bo nie są dostępne w desktopowych przeglądarkach – mimo że niekoniecznie mają w nich sens (np. wspomniane już Web NFC).</p>
<p>Mimo swoich wad, Baseline już jest stałym elementem choćby dokumentacji MDN czy <a href="https://web.dev/blog/baseline-urlpattern?hl=en" rel="noreferrer noopener">Web.dev</a>. Osobiście mam co do niego mieszane uczucia, ale nie można mu odmówić jednego: robi dokładnie to, do czego go stworzono. A przez to już jest lepszy od wielu technologii sieciowych…</p>
]]></content>
		</entry>
	
		<entry>
			<title type="html">Ufam ci!</title>
			
				<author>
					<name>Comandeer</name>
				</author>
			
			<link href="https://blog.comandeer.pl/ufam-ci" rel="alternate" type="text/html"/>
			<published>2025-12-18T23:00:00.000Z</published>
			<updated>2025-12-18T23:00:00.000Z</updated>
			<id>https://blog.comandeer.pl/ufam-ci</id>
			
				<summary><![CDATA[HTML Sanitizer API to tylko jedna z metod obrony przed XSS w przeglądarkach. Drugą jest Trusted Types API.]]></summary>
			
			<content type="html"><![CDATA[<p><a href="https://blog.comandeer.pl/dezynfekcja">HTML Sanitizer API</a> jest dobrą i skuteczną&nbsp;odpowiedzią na problem ataków XSS. Ma jednak jedną, zasadniczą wadę: jego dodanie do już istniejących aplikacji wymaga ingerencji w kod. I to często w wielu miejscach. Na szczęście przeglądarki udostępniają jeszcze jedno API, które sprawdzi się o wiele lepiej w takiej sytuacji.</p>
<h2 id="problem-z-istniejącymi-aplikacjami"><a class="header-anchor" href="https://blog.comandeer.pl/ufam-ci#problem-z-istniejącymi-aplikacjami">Problem z istniejącymi aplikacjami</a></h2>
<p>Żeby zabezpieczyć aplikację przy pomocy HTML Sanitizer API, trzeba zadbać o to, żeby wszystkie miejsca, w których dodajemy HTML, korzystały z nowej <a href="https://developer.mozilla.org/en-US/docs/Web/API/Element/setHTML" rel="noreferrer noopener">metody <code>#setHTML()</code></a>. Innymi słowy: wszystkie wystąpienia <a href="https://developer.mozilla.org/en-US/docs/Web/API/Element/innerHTML" rel="noreferrer noopener">własności <code>#innerHTML</code></a> trzeba podmienić. I choć na pierwszy rzut oka nie wydaje się to specjalnie skomplikowane, to z powodu złożoności aplikacji może to nastręczyć sporo problemów.</p>
<p>Pierwszym z nich jest zmiana zachowania aplikacji. Dotąd HTML był ustawiany bez dodatkowego czyszczenia. Teraz jest filtrowany. W zależności od aplikacji, może to być problemem. Weźmy takiego <a href="https://ckeditor.com/" rel="noreferrer noopener">CKEditora</a>. Jeśli nagle przestanie działać dodawanie obrazków w edytorze (bo HTML Sanitizer APi je wytnie), to on sam stanie się średnio użyteczny. Stąd proste podmienienie własności <code>#innerHTML</code> na metodę <code>#setHTML()</code> w takim wypadku nie zadziała.</p>
<p>Drugi z problemów związany jest z samym refactorem. Bo to prawie nigdy nie jest po prostu podmiana czegoś w kodzie. Często trzeba też zmienić choćby testy, które mogą zacząć padać po zmianie zachowania aplikacji. Często też trzeba sprawdzić, czemu testy <em>nie</em> zaczęły padać – by się upewnić, że na pewno wszystko dobrze testują.</p>
<p>Trzeci problem to zależności aplikacji. Nasz kod możemy w ten czy inny sposób zrefactoryzować. Ale z zależnościami nie jest już&nbsp;tak łatwo i najczęściej kończy się na czekaniu, aż dany projekt doda wsparcie dla nowego API. A na to se można poczekać – o ile dany projekt w ogóle będzie chciał taką zmianę wprowadzić.</p>
<p>I wreszcie: kompatybilność. Dopóki HTML Sanitizer API nie będzie miało sensownego wsparcia we wszystkich najważniejszych przeglądarkach, trzeba będzie zadbać o alternatywę. Najprawdopodobniej w postaci <a href="https://developer.mozilla.org/en-US/docs/Glossary/Polyfill" rel="noreferrer noopener">polyfilla</a>. A to dodatkowo niepotrzebnie skomplikuje kod i dołoży kolejną&nbsp;zależność do projektu.</p>
<p>Innymi słowy: wdrożenie HTML Sanitizer API może być sporym wyzwaniem w istniejących projektach.</p>
<h2 id="trusted-types-api"><a class="header-anchor" href="https://blog.comandeer.pl/ufam-ci#trusted-types-api">Trusted Types API</a></h2>
<p>Na szczęście istnieje inne API, które ma zdecydowanie lepsze wsparcie i które można wpiąć w już istniejące aplikacje przy minimalnej ingerencji. To API to <a href="https://developer.mozilla.org/en-US/docs/Web/API/Trusted_Types_API" rel="noreferrer noopener"><i lang="en">Trusted Types API</i> (API Zaufanych Typów)</a>. Pozwala ono zabezpieczyć tzw. <i lang="en">injection sinks</i> (miejsca wstrzykiwania) przy pomocy polityk.</p>
<h3 id="miejsca-wstrzykiwania"><a class="header-anchor" href="https://blog.comandeer.pl/ufam-ci#miejsca-wstrzykiwania">Miejsca wstrzykiwania</a></h3>
<p>Miejsca wstrzykiwania to wszystkie te fragmenty kodu, w których do strony WWW jest dodawana treść lub może być wykonany skrypt JS. Najprostszym przykładem jest własność&nbsp;<code>#innerHTML</code>, przy pomocy której można wsadzić HTML do wybranego elementu. Są też jednak mniej oczywiste przykłady:</p>
<ul>
<li><a href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLScriptElement/text" rel="noreferrer noopener">własność <code>HTMLScriptElement#text</code></a> – służąca do zmiany zawartości elementu <code>script</code>,</li>
<li><a href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLScriptElement/src" rel="noreferrer noopener">własność <code>HTMLScriptElement#src</code></a> – służąca do zmiany źródła elementu <code>script</code>,</li>
<li><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/eval" rel="noreferrer noopener">funkcja <code>eval()</code></a> – wykonująca przekazany jej kod JS,</li>
<li><a href="https://developer.mozilla.org/en-US/docs/Web/API/DOMParser/parseFromString" rel="noreferrer noopener">metoda <code>DOMParser#parseFromString()</code></a> – parsująca przekazany kod HTML,</li>
<li><a href="https://developer.mozilla.org/en-US/docs/Web/API/Element/setHTMLUnsafe" rel="noreferrer noopener">metoda <code>setHTMLUnsafe()</code></a> – służąca do zmiany zawartości elementu z pominięciem czyszczenia przekazanego kodu.</li>
</ul>
<p>Takie przykłady można by mnożyć. MDN zawiera sporą <a href="https://developer.mozilla.org/en-US/docs/Web/API/Trusted_Types_API#injection_sink_interfaces" rel="noreferrer noopener">listę potencjalnych miejsc wstrzykiwania</a>.</p>
<h3 id="polityki"><a class="header-anchor" href="https://blog.comandeer.pl/ufam-ci#polityki">Polityki</a></h3>
<p>Trzonem Trusted Types API są tzw. polityki. To obiekty zawierające konfigurację opisują, w jaki sposób mają być zabezpieczone poszczególne miejsca wstrzykiwania. Każda polityka może składać się z trzech <a href="https://refactoring.guru/design-patterns/factory-method" rel="noreferrer noopener">metod-fabryk</a>:</p>
<ul>
<li><a href="https://developer.mozilla.org/en-US/docs/Web/API/TrustedTypePolicy/createHTML" rel="noreferrer noopener"><code>#createHTML()</code></a> – służąca do tworzenia <a href="https://developer.mozilla.org/en-US/docs/Web/API/TrustedHTML" rel="noreferrer noopener">obiektów <code>TrustedHTML</code></a>, które mogą być później używane wszędzie tam, gdzie trzeba przekazać kod HTML, np. własność <code>#innerHTML</code> lub metoda <code>#setHTMLUnsafe()</code>,</li>
<li><a href="https://developer.mozilla.org/en-US/docs/Web/API/TrustedTypePolicy/createScript" rel="noreferrer noopener"><code>#createScript()</code></a> – służąca do tworzenia <a href="https://developer.mozilla.org/en-US/docs/Web/API/TrustedScript" rel="noreferrer noopener">obiektów <code>TrustedScript</code></a>, które mogą być później używane tam, gdzie jest potrzebny kod JS, np. wewnątrz funkcji <code>eval()</code>,</li>
<li><a href="https://developer.mozilla.org/en-US/docs/Web/API/TrustedTypePolicy/createScriptURL" rel="noreferrer noopener"><code>#createScriptURL()</code></a> – służąca do tworzenia <a href="https://developer.mozilla.org/en-US/docs/Web/API/TrustedScriptURL" rel="noreferrer noopener">obiektów <code>TrustedScriptURL</code></a>, które mogą być później używane tam, gdzie potrzebny jest adres skryptu JS, np. we własności <code>HTMLScriptElement#src</code>.</li>
</ul>
<p>Żeby dodać nową politykę, trzeba skorzystać z <a href="https://developer.mozilla.org/en-US/docs/Web/API/TrustedTypePolicyFactory/createPolicy" rel="noreferrer noopener">metody <code>trustedTypes#createPolicy()</code></a>:</p>
<figure class="code" typeof="SoftwareSourceCode">
			<figcaption class="code__caption">
				<span class="code__title" property="programmingLanguage">JavaScript</span>
				
			</figcaption>
			<div class="code__code" translate="no" property="text">
				<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">const</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> policy</span><span style="color:#D73A49;--shiki-dark:#F97583"> =</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> trustedTypes.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">createPolicy</span><span style="color:#24292E;--shiki-dark:#E1E4E8">( </span><span style="color:#032F62;--shiki-dark:#9ECBFF">'my-policy'</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, { </span><span style="color:#6A737D;--shiki-dark:#6A737D">// 1</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">	createHTML</span><span style="color:#24292E;--shiki-dark:#E1E4E8">( </span><span style="color:#E36209;--shiki-dark:#FFAB70">html</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> ) { </span><span style="color:#6A737D;--shiki-dark:#6A737D">// 2</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">		return</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> sanitizeHTML</span><span style="color:#24292E;--shiki-dark:#E1E4E8">( html );</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">	},</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">	createScript</span><span style="color:#24292E;--shiki-dark:#E1E4E8">( </span><span style="color:#E36209;--shiki-dark:#FFAB70">js</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> ) { </span><span style="color:#6A737D;--shiki-dark:#6A737D">// 3</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">		return</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> sanitizeJS</span><span style="color:#24292E;--shiki-dark:#E1E4E8">( js );</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">	},</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">	createScriptURL</span><span style="color:#24292E;--shiki-dark:#E1E4E8">( </span><span style="color:#E36209;--shiki-dark:#FFAB70">url</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> ) { </span><span style="color:#6A737D;--shiki-dark:#6A737D">// 4</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">		return</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> sanitizeURL</span><span style="color:#24292E;--shiki-dark:#E1E4E8">( url );</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">	}</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">} );</span></span>
<span class="line"></span></code></pre>

			</div>
		</figure><p>Tworzymy nową politykę o nazwie <code>my-policy</code> i zapisujemy ją do zmiennej <code>policy</code> (1). Ma ona wszystkie trzy metody – <code>#createHTML()</code> (2), <code>#createScript()</code> (3) oraz <code>#createScriptURL()</code> (4). Zwracają one przekazany im kod przepuszczony przez odpowiednie funkcje filtrujące (5, 6, 7 ). To, co te funkcje robią, zależy w zupełności od nas. W końcu każda aplikacja jest inna i może mieć inne wymagania odnośnie czyszczenia HTML-a czy JS-a. Na potrzeby przykładu stwórzmy zaślepki, które po prostu zwrócą przekazany im argument:</p>
<figure class="code" typeof="SoftwareSourceCode">
			<figcaption class="code__caption">
				<span class="code__title" property="programmingLanguage">JavaScript</span>
				
			</figcaption>
			<div class="code__code" translate="no" property="text">
				<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">function</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> sanitizeHTML</span><span style="color:#24292E;--shiki-dark:#E1E4E8">( </span><span style="color:#E36209;--shiki-dark:#FFAB70">html</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> ) {</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">	return</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> html;</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">}</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">function</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> sanitizeJS</span><span style="color:#24292E;--shiki-dark:#E1E4E8">( </span><span style="color:#E36209;--shiki-dark:#FFAB70">js</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> ) {</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">	return</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> js;</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">}</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">function</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> sanitizeURL</span><span style="color:#24292E;--shiki-dark:#E1E4E8">( </span><span style="color:#E36209;--shiki-dark:#FFAB70">url</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> ) {</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">	return</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> url;</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">}</span></span>
<span class="line"></span></code></pre>

			</div>
		</figure><p>Żeby wykorzystać w praktyce naszą politykę, trzeba wywołać jej metody:</p>
<figure class="code" typeof="SoftwareSourceCode">
			<figcaption class="code__caption">
				<span class="code__title" property="programmingLanguage">JavaScript</span>
				
			</figcaption>
			<div class="code__code" translate="no" property="text">
				<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">const</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> div</span><span style="color:#D73A49;--shiki-dark:#F97583"> =</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> document.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">createElement</span><span style="color:#24292E;--shiki-dark:#E1E4E8">( </span><span style="color:#032F62;--shiki-dark:#9ECBFF">'div'</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> ); </span><span style="color:#6A737D;--shiki-dark:#6A737D">// 1</span></span>
<span class="line"></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">div.innerHTML </span><span style="color:#D73A49;--shiki-dark:#F97583">=</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> policy.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">createHTML</span><span style="color:#24292E;--shiki-dark:#E1E4E8">( </span><span style="color:#032F62;--shiki-dark:#9ECBFF">'&lt;p&gt;Jestem zaufanym HTML-em&lt;/p&gt;'</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> ); </span><span style="color:#6A737D;--shiki-dark:#6A737D">// 2</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">eval</span><span style="color:#24292E;--shiki-dark:#E1E4E8">( policy.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">createScript</span><span style="color:#24292E;--shiki-dark:#E1E4E8">( </span><span style="color:#032F62;--shiki-dark:#9ECBFF">'alert(1)'</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> ) ); </span><span style="color:#6A737D;--shiki-dark:#6A737D">// 3</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">const</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> script</span><span style="color:#D73A49;--shiki-dark:#F97583"> =</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> document.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">createElement</span><span style="color:#24292E;--shiki-dark:#E1E4E8">( </span><span style="color:#032F62;--shiki-dark:#9ECBFF">'script'</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> ); </span><span style="color:#6A737D;--shiki-dark:#6A737D">// 4</span></span>
<span class="line"></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">script.src </span><span style="color:#D73A49;--shiki-dark:#F97583">=</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> policy.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">createScriptURL</span><span style="color:#24292E;--shiki-dark:#E1E4E8">( </span><span style="color:#032F62;--shiki-dark:#9ECBFF">'data:text/javascript;charset=utf-8;base64,YWxlcnQoMSk='</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> ); </span><span style="color:#6A737D;--shiki-dark:#6A737D">// 5</span></span>
<span class="line"></span></code></pre>

			</div>
		</figure><p>Tworzymy element <code>div</code> (1), a następnie wsadzamy do niego “zaufany” HTML, stworzony przy pomocy metody <code>policy#createHTML()</code> (2). Z kolei do funkcji <code>eval()</code> przekazujemy “zaufany” skrypt, stworzony przy pomocy metody <code>policy#createScript()</code> (3). Na koniec tworzymy element <code>script</code> (4) i ustawiamy jego własność <code>#src</code> na “zaufany” URL skryptu stworzony przy pomocy metody <code>policy#createScriptURL()</code> (5).</p>
<h3 id="wymuszanie-polityki"><a class="header-anchor" href="https://blog.comandeer.pl/ufam-ci#wymuszanie-polityki">Wymuszanie polityki</a></h3>
<p>Samo jednak dodanie polityki nie zabezpieczy aplikacji. Przeglądarka nie będzie miała żadnego problemu z tym, żeby ją ominąć i ustawić własność <code>#innerHTML</code> na dowolny tekst. Dlatego API dostarcza też mechanizm, który wymusza stosowanie polityki. Służy do tego <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Content-Security-Policy/require-trusted-types-for" rel="noreferrer noopener">dyrektywa <code>require-trusted-types-for</code></a> w <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Content-Security-Policy" rel="noreferrer noopener">nagłówku HTTP <code>Content-Security-Policy</code></a>:</p>
<figure class="code" typeof="SoftwareSourceCode">
			<figcaption class="code__caption">
				<span class="code__title" property="programmingLanguage"></span>
				
			</figcaption>
			<div class="code__code" translate="no" property="text">
				<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span>Content-Security-Policy: require-trusted-types-for 'script';</span></span>
<span class="line"><span></span></span></code></pre>

			</div>
		</figure><p>Jak na razie jedyną wartością dla tej dyrektywy jest <code>'script'</code>, która pokrywa wszystkie trzy przypadki (kod HTML, kod JS i URL-e skryptów).</p>
<p>Jeśli wyślemy taki nagłówek HTTP, a następnie spróbujemy wstawić niezabezpieczony tekst do własności <code>#innerHTML</code>, powinniśmy otrzymać następujący błąd:</p>
<figure class="code" typeof="SoftwareSourceCode">
			<figcaption class="code__caption">
				<span class="code__title" property="programmingLanguage"></span>
				
			</figcaption>
			<div class="code__code" translate="no" property="text">
				<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span>This document requires 'TrustedHTML' assignment. The action has been blocked.</span></span>
<span class="line"><span></span></span></code></pre>

			</div>
		</figure><p>Istnieje też <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Content-Security-Policy/trusted-types" rel="noreferrer noopener">dyrektywa <code>trusted-types</code></a>, która kontroluje tworzenie polityk:</p>
<figure class="code" typeof="SoftwareSourceCode">
			<figcaption class="code__caption">
				<span class="code__title" property="programmingLanguage"></span>
				
			</figcaption>
			<div class="code__code" translate="no" property="text">
				<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span>Content-Security-Policy: require-trusted-types-for 'script';trusted-types my-policy;</span></span>
<span class="line"><span></span></span></code></pre>

			</div>
		</figure><p>Taki nagłówek wymusi używanie polityki, a równocześnie pozwoli stworzyć jedynie taką o nazwie <code>my-policy</code>.</p>
<h3 id="domyślna-polityka"><a class="header-anchor" href="https://blog.comandeer.pl/ufam-ci#domyślna-polityka">Domyślna polityka</a></h3>
<p>Ale zaraz, zaraz – czy to API nie miało być tym “mniej inwazyjnym”, które nie wymaga sporego refactoru istniejących aplikacji? Takie tworzenie polityk wydaje się wręcz jeszcze bardziej upierdliwe, niż prosta zamiana <code>#innerHTML</code> na <code>#setHTML()</code>. Na całe szczęście Trusted Types API ma asa w rękawie: domyślną politykę. Jeśli wymusimy stosowanie polityk przy pomocy dyrektywy <code>require-trusted-types-for</code>, a następnie stworzymy politykę o nazwie <code>default</code>, będzie ona automatycznie wykorzystywana w każdym miejscu wstrzykiwania:</p>
<figure class="code" typeof="SoftwareSourceCode">
			<figcaption class="code__caption">
				<span class="code__title" property="programmingLanguage">JavaScript</span>
				
			</figcaption>
			<div class="code__code" translate="no" property="text">
				<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">trustedTypes.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">createPolicy</span><span style="color:#24292E;--shiki-dark:#E1E4E8">( </span><span style="color:#032F62;--shiki-dark:#9ECBFF">'default'</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, { </span><span style="color:#6A737D;--shiki-dark:#6A737D">// 1</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">	createHTML</span><span style="color:#24292E;--shiki-dark:#E1E4E8">( </span><span style="color:#E36209;--shiki-dark:#FFAB70">html</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> ) {</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">		console.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">log</span><span style="color:#24292E;--shiki-dark:#E1E4E8">( </span><span style="color:#032F62;--shiki-dark:#9ECBFF">'HTML'</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> ); </span><span style="color:#6A737D;--shiki-dark:#6A737D">// 5</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">		return</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> html; </span><span style="color:#6A737D;--shiki-dark:#6A737D">// 2</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">	},</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">	createScript</span><span style="color:#24292E;--shiki-dark:#E1E4E8">( </span><span style="color:#E36209;--shiki-dark:#FFAB70">js</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> ) {</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">		console.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">log</span><span style="color:#24292E;--shiki-dark:#E1E4E8">( </span><span style="color:#032F62;--shiki-dark:#9ECBFF">'JS'</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> ); </span><span style="color:#6A737D;--shiki-dark:#6A737D">// 6</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">		return</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> js; </span><span style="color:#6A737D;--shiki-dark:#6A737D">// 3</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">	},</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">	createScriptURL</span><span style="color:#24292E;--shiki-dark:#E1E4E8">( </span><span style="color:#E36209;--shiki-dark:#FFAB70">url</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> ) {</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">		console.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">log</span><span style="color:#24292E;--shiki-dark:#E1E4E8">( </span><span style="color:#032F62;--shiki-dark:#9ECBFF">'URL'</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> ); </span><span style="color:#6A737D;--shiki-dark:#6A737D">// 7</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">		return</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> url; </span><span style="color:#6A737D;--shiki-dark:#6A737D">// 4</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">	}</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">} );</span></span>
<span class="line"></span></code></pre>

			</div>
		</figure><p>Tworzymy nową politykę o nazwie <code>'default'</code> (1). Ponownie tworzymy metody-fabryki, w których zwracamy przekazany im kod (2, 3, 4). Wcześniej jednak logujemy w konsoli, jaki typ danych filtrujemy (5, 6, 7)</p>
<p>Wykorzystanie tej polityki jest całkowicie przezroczyste:</p>
<figure class="code" typeof="SoftwareSourceCode">
			<figcaption class="code__caption">
				<span class="code__title" property="programmingLanguage">JavaScript</span>
				
			</figcaption>
			<div class="code__code" translate="no" property="text">
				<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">const</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> div</span><span style="color:#D73A49;--shiki-dark:#F97583"> =</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> document.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">createElement</span><span style="color:#24292E;--shiki-dark:#E1E4E8">( </span><span style="color:#032F62;--shiki-dark:#9ECBFF">'div'</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> );</span></span>
<span class="line"></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">div.innerHTML </span><span style="color:#D73A49;--shiki-dark:#F97583">=</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> '&lt;p&gt;Jestem zaufanym HTML-em&lt;/p&gt;'</span><span style="color:#24292E;--shiki-dark:#E1E4E8">; </span><span style="color:#6A737D;--shiki-dark:#6A737D">// 1</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">eval</span><span style="color:#24292E;--shiki-dark:#E1E4E8">( </span><span style="color:#032F62;--shiki-dark:#9ECBFF">'alert(1)'</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> ); </span><span style="color:#6A737D;--shiki-dark:#6A737D">// 2</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">const</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> script</span><span style="color:#D73A49;--shiki-dark:#F97583"> =</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> document.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">createElement</span><span style="color:#24292E;--shiki-dark:#E1E4E8">( </span><span style="color:#032F62;--shiki-dark:#9ECBFF">'script'</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> );</span></span>
<span class="line"></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">script.src </span><span style="color:#D73A49;--shiki-dark:#F97583">=</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> 'data:text/javascript;charset=utf-8;base64,YWxlcnQoMSk='</span><span style="color:#24292E;--shiki-dark:#E1E4E8">; </span><span style="color:#6A737D;--shiki-dark:#6A737D">// 3</span></span>
<span class="line"></span></code></pre>

			</div>
		</figure><p>Nie trzeba się już odwoływać do polityki ani przy przypisywaniu wartości do własności <code>#innerHTML</code> (1), ani przy wywoływaniu funkcji <code>eval()</code> (2), ani przy ustawianiu źródła skryptu (3).</p>
<p>Jeśli odpalimy ten kod w przeglądarce, wówczas powinniśmy zobaczyć w konsoli logi potwierdzające, że polityka została zaaplikowana do każdego z trzech miejsc wstrzykiwania.</p>
<h3 id="kompatybilność"><a class="header-anchor" href="https://blog.comandeer.pl/ufam-ci#kompatybilność">Kompatybilność</a></h3>
<p><a href="https://w3c.github.io/trusted-types/dist/spec/" rel="noreferrer noopener">Specyfikacja Trusted Types API</a> jest już na dość zaawansowanym etapie standaryzacji. Choć wciąż może się zmieniać,  nie oczekiwałbym jakichś wielkich, wywrotowych zmian. Jak już jakieś nastąpią, to raczej kosmetyczne. Natomiast <a href="https://caniuse.com/trusted-types" rel="noreferrer noopener">wsparcie</a> jest już od dłuższego czasu zarówno w Chrome, jak i w Safari. W Firefoksie wciąż jest eksperymentalne za flagą.</p>
<p>Myślę, że dopóki wsparcie nie będzie we wszystkich najważniejszych przeglądarkach, najlepiej się ograniczyć do tworzenia domyślnej polityki i wymuszać ją przy pomocy dyrektywy <code>require-trusted-types-for</code>. Dzięki temu całość będzie jak najbardziej transparentna dla przeglądarek, które wciąż nie wspierają tego API. Natomiast cała reszta dostanie lepsze zabezpieczenie przed atakami XSS.</p>
<p>I w końcu będzie można zacząć&nbsp;ufać własności <code>#innerHTML</code>!</p>
]]></content>
		</entry>
	
		<entry>
			<title type="html">Agenci wpływu</title>
			
				<author>
					<name>Comandeer</name>
				</author>
			
			<link href="https://blog.comandeer.pl/agenci-wplywu" rel="alternate" type="text/html"/>
			<published>2025-12-17T23:00:00.000Z</published>
			<updated>2025-12-17T23:00:00.000Z</updated>
			<id>https://blog.comandeer.pl/agenci-wplywu</id>
			
				<summary><![CDATA[TAG wypuścił ostatnio definicję agenta osoby użytkowniczej – przyjrzyjmy się jej!]]></summary>
			
			<content type="html"><![CDATA[<p>Określenie <i lang="en">user agent</i> (agent osoby użytkowniczej) na stałe już&nbsp;wrosło w tkankę Sieci. Jednak najczęściej jest używane jako <a href="https://gwd.comandeer.pl/przegladarki/" rel="noreferrer noopener">brzmiący profesjonalniej synonim słowa przeglądarka</a>.  Tymczasem <a href="https://w3ctag.github.io/user-agents/" rel="noreferrer noopener">TAG proponuje (przypomina?) o wiele szersze znaczenie</a>.</p>
<h2 id="definicja"><a class="header-anchor" href="https://blog.comandeer.pl/agenci-wplywu#definicja">Definicja</a></h2>
<p>TAG proponuje następującą <a href="https://w3ctag.github.io/user-agents/#what" rel="noreferrer noopener">definicję agenta osoby użytkowniczej</a>:</p>
<blockquote>
<p lang="en">A <dfn>web user agent</dfn> is any software entity that interacts with websites outside the entity itself, on behalf of its user, including simply rendering the content of websites. […] The most common type of web user agent is the web browser, including in-app browsers that can follow cross-site links. However, user agents also include other tools like search engines, voice-driven assistants, and generative AI systems that present snippets or summaries of website content, or help people navigate and interact with websites.</p>
<p>[<dfn>Sieciowy agent osoby użytkowniczej</dfn> to każde oprogramowanie, wchodzące w interakcje ze stronami WWW, pozostającymi poza tym oprogramowaniem, na polecenie swojej osoby użytkowniczej; interakcje obejmują <a href="http://m.in" rel="noreferrer noopener">m.in</a>. samo renderowanie zawartości stron. […] Najpopularniejszym typem sieciowego agenta osoby użytkowniczej jest przeglądarka, włączając w to przeglądarki wbudowane w aplikacje, mogące korzystać z linków między stronami. Niemniej termin “agenty osoby użytkowniczej” zawiera także inne narzędzia, takie jak wyszukiwarki, asystenci do sterowania głosem oraz systemy generatywnego AI, które prezentują wycinki lub podsumowania zawartości stron lub pomagają osobom nawigować i wchodzić w interakcje ze stronami.]</p>
</blockquote>
<p>Brzmi to strasznie zawile, więc spróbujmy to uprościć. W największym skrócie: sieciowy agent osoby użytkowniczej to dowolny progam, dzięki któremu można coś&nbsp;zrobić ze stroną WWW. Przeglądarka spełnia tę definicję, ponieważ pozwala wyświetlać&nbsp;stronę WWW. Wyszukiwarka spełnia tę definicję, bo umożliwia znalezienie konkretnej strony lub przeszukanie jej treści. ChatGPT też&nbsp;spełnia tę definicję, ponieważ może podsumować&nbsp;treść&nbsp;strony.</p>
<p>Warto też&nbsp;zwrócić uwagę, że w definicji pada sformułowanie <em>sieciowy</em> agent. Jak bowiem TAG zauważa, istnieć mogą też inne agenty, np. mailowy agent – a więc taki, przez którego wyślemy i odbierzemy maila.</p>
<p>Oprócz definicji TAG wymienia też <a href="https://w3ctag.github.io/user-agents/#duties" rel="noreferrer noopener">trzy obowiązki agenta</a>:</p>
<ul>
<li><a href="https://w3ctag.github.io/user-agents/#protection" rel="noreferrer noopener"><strong>ochrona</strong></a> – interakcje ze stronami WWW muszą&nbsp;być bezpieczne, czyli np. strona nie może bez pozwolenia zmieniać ustawień komputera, a przeglądarka nie może sama z siebie wysłać stronie numeru karty kredytowej swojej osoby użytkowniczej;</li>
<li><a href="https://w3ctag.github.io/user-agents/#honesty" rel="noreferrer noopener"><strong>szczerość</strong></a> – agent powinien zawsze informować osobę użytkowniczą, co się dzieje, i to w sposób dla niej zrozumiały, np. pasek adresu w przeglądarce powinien pokazywać poprawny adres obecnie przeglądanej strony;</li>
<li><a href="https://w3ctag.github.io/user-agents/#loyalty" rel="noreferrer noopener"><strong>lojalność</strong></a> – agent powinien przedkładać interes swojej osoby użytkowniczej ponad interes kogokolwiek innego, np. przeglądarka wbudowana w aplikację nie może utrudniać próby otwarcia strony w innej przeglądarce tylko po to, by zatrzymać&nbsp;kogoś wewnątrz siebie.</li>
</ul>
<h2 id="tag"><a class="header-anchor" href="https://blog.comandeer.pl/agenci-wplywu#tag">TAG?</a></h2>
<p>No dobrze, ale czym jest ten cały TAG? TAG, a dokładniej <a href="https://tag.w3.org/" rel="noreferrer noopener">W3C Technical Architecture Group (Grupa Architektury Technicznej W3C)</a>, to trochę&nbsp;taka starszyzna standardów sieciowych. W jej skład wchodzą osoby wybierane na dwuletnie kadencje&nbsp;przez członków W3C (czyli firmy zgromadzone w W3C) lub wskazywane przez zespół W3C oraz <a href="https://en.wikipedia.org/wiki/Tim_Berners-Lee" rel="noreferrer noopener">Tim Berners-Lee</a>, jedyny dożywotni członek. <a href="https://www.w3.org/policies/process/#tag-role" rel="noreferrer noopener">Zadanie TAG</a> jest ściśle określone i sprowadza się&nbsp;przede wszystkim do doradzania innym grupom, w jaki sposób rozwiązywać techniczne zagwozdki, oraz do dokumentowania zasad, jakie powinny przyświecać standardom sieciowym. Przykładem tej drugiej części działalności grupy jest definicja agenta osoby użytkowniczej, ale też <a href="https://w3ctag.github.io/ethical-web-principles/" rel="noreferrer noopener"><cite lang="en">Ethical Web Principles</cite> (Zasady Etycznej Sieci)</a>. Z kolei przykładem pierwszej są&nbsp;<a href="https://github.com/w3ctag/design-reviews" rel="noreferrer noopener">publiczne dyskusje nad propozycjami nowych standardów</a>.</p>
<p>Co prawda TAG nie bierze bezpośredniego udziału w tworzeniu standardów, a ma jedynie doradzać, jednak uwagi zgłaszane przez tę grupę najczęściej są brane pod uwagę. W TAG-u siedzą ludzie, którzy zjedli zęby na standardach sieciowych i są w stanie zauważyć niedoskonałości w większości pokazywanych im API. Czy to na poziomie czysto technicznym, czy też na poziomie np. potencjalnych zagrożeń prywatności. Mając osoby z takim doświadczeniem na wyciągnięcie ręki, głupio byłoby z ich wsparcia nie skorzystać.</p>
<p>Więc korzystajmy!</p>
]]></content>
		</entry>
	
		<entry>
			<title type="html">Dezynfekcja</title>
			
				<author>
					<name>Comandeer</name>
				</author>
			
			<link href="https://blog.comandeer.pl/dezynfekcja" rel="alternate" type="text/html"/>
			<published>2025-12-16T23:00:00.000Z</published>
			<updated>2025-12-16T23:00:00.000Z</updated>
			<id>https://blog.comandeer.pl/dezynfekcja</id>
			
				<summary><![CDATA[Rzut oka na HTML Sanitizer API – kolejną broń w arsenale przeciwko atakom XSS.]]></summary>
			
			<content type="html"><![CDATA[<p>Internet nie jest bezpiecznym miejscem. Zagrożenia czyhają na każdym rogu. Jednym z nich są ataki XSS, które wymierzone są równocześnie w stronę&nbsp;WWW, jak i przeglądarkę. Na szczęście powstało nowe API, mające pomóc w ochronie przed nimi!</p>
<h2 id="xss--czyli-co-zagraża-aplikacji-www"><a class="header-anchor" href="https://blog.comandeer.pl/dezynfekcja#xss--czyli-co-zagraża-aplikacji-www">XSS – czyli co zagraża aplikacji WWW?</a></h2>
<p>Zacznijmy od tego, czym w ogóle jest atak <a href="https://developer.mozilla.org/en-US/docs/Web/Security/Attacks/XSS" rel="noreferrer noopener">Cross-Site Scripting (XSS)</a>. W największym skrócie: to atak polegający na wstrzyknięciu złośliwego kodu na stronę WWW. Ten kod następnie zostanie wykonany przez przeglądarkę osoby odwiedzającej stronę. Taki kod może zrobić tak naprawdę wszystko, co potrafi JS w przeglądarce, np. pobranie ciasteczek i wysłanie ich do zewnętrznego serwera.</p>
<p>Wstrzyknięcia można dokonać na wiele różnych sposobów. Jednym z najczęstszych jest wykorzystanie formularzy na stronie, np. edytora postów na blogu czy księgi gości. Jeśli dostęp do takiego formularza mają też niezaufane osoby, wówczas jest spora szansa, że jedna z nich spróbuje zaatakować stronę. Choćby zostawiając poniższy wpis:</p>
<figure class="code" typeof="SoftwareSourceCode">
			<figcaption class="code__caption">
				<span class="code__title" property="programmingLanguage">HTML</span>
				
			</figcaption>
			<div class="code__code" translate="no" property="text">
				<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">&lt;</span><span style="color:#22863A;--shiki-dark:#85E89D">img</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> src</span><span style="color:#24292E;--shiki-dark:#E1E4E8">=</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"x"</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> onerror</span><span style="color:#24292E;--shiki-dark:#E1E4E8">=</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"</span><span style="color:#6F42C1;--shiki-dark:#B392F0">alert</span><span style="color:#032F62;--shiki-dark:#9ECBFF">( 'Słaba ta strona!' )"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">&gt;</span></span>
<span class="line"></span></code></pre>

			</div>
		</figure><p>Jeśli w żaden sposób nie filtrujemy danych przychodzących, tego typu kod może trafić do bazy danych, a ostatecznie – do przeglądarek innych osób odwiedzających stronę. Każdej z tych osób pokaże się wówczas wyskakujące okienko z tekstem <q>Słaba ta strona!</q>. Dzieje się tak, ponieważ atrybut <code>[onerror]</code> dodaje do obrazka nasłuchiwacz dla <a href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/error_event" rel="noreferrer noopener">zdarzenia <code>error</code></a>. A to odpala się <a href="http://m.in" rel="noreferrer noopener">m.in</a> wówczas, gdy wczytywany obrazek nie istnieje. Jeśli nie mamy na stronie obrazka pod ścieżką <code>/x</code> (a mało kto ma), to zdarzenie się odpali i kod wykona się u niczego niepodejrzewających osób odwiedzających naszą stronę.</p>
<h2 id="istniejące-sposoby-ochrony"><a class="header-anchor" href="https://blog.comandeer.pl/dezynfekcja#istniejące-sposoby-ochrony">Istniejące sposoby ochrony</a></h2>
<p>Mimo że XSS to jeden z podstawowych typów ataków, niemal tak stary jak sam Internet, to wciąż jest niezwykle popularny. Wciąż mocno się&nbsp;trzyma na <a href="https://owasp.org/www-project-top-ten/" rel="noreferrer noopener">liście Top 10 OWASP</a> – raporcie opisującym dziesięć najpopularniejszych typów ataków w Internecie. Dlatego też wciąż potrzeba sensownych i sprawdzonych metod obrony przed nim. Dotąd trzeba było wykorzystywać w tym celu różne biblioteki. Jedną z najpopularniejszych jest <a href="https://github.com/cure53/DOMPurify" rel="noreferrer noopener">DOMPurify</a>, które działa nie tylko w przeglądarce, ale także w Node.js. Działanie tej biblioteki można zobaczyć w <a href="https://cure53.de/purify" rel="noreferrer noopener">oficjalnym demie</a>. Polega na usuwaniu z przekazanego ciągu tekstowego wszystkich niebezpiecznych fragmentów HTML-a. Jako przykład posłuży nam następujący kod:</p>
<figure class="code" typeof="SoftwareSourceCode">
			<figcaption class="code__caption">
				<span class="code__title" property="programmingLanguage">HTML</span>
				
			</figcaption>
			<div class="code__code" translate="no" property="text">
				<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">&lt;</span><span style="color:#22863A;--shiki-dark:#85E89D">script</span><span style="color:#24292E;--shiki-dark:#E1E4E8">&gt;</span><span style="color:#6F42C1;--shiki-dark:#B392F0">alert</span><span style="color:#24292E;--shiki-dark:#E1E4E8">( </span><span style="color:#032F62;--shiki-dark:#9ECBFF">'script'</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> );&lt;/</span><span style="color:#22863A;--shiki-dark:#85E89D">script</span><span style="color:#24292E;--shiki-dark:#E1E4E8">&gt; </span><span style="color:#6A737D;--shiki-dark:#6A737D">&lt;!-- 1 --&gt;</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">&lt;</span><span style="color:#22863A;--shiki-dark:#85E89D">img</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> src</span><span style="color:#24292E;--shiki-dark:#E1E4E8">=</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"x"</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> alt</span><span style="color:#24292E;--shiki-dark:#E1E4E8">=</span><span style="color:#032F62;--shiki-dark:#9ECBFF">""</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> onerror</span><span style="color:#24292E;--shiki-dark:#E1E4E8">=</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"</span><span style="color:#6F42C1;--shiki-dark:#B392F0">alert</span><span style="color:#032F62;--shiki-dark:#9ECBFF">('obrazek')"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">&gt; </span><span style="color:#6A737D;--shiki-dark:#6A737D">&lt;!-- 2 --&gt;</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">&lt;</span><span style="color:#22863A;--shiki-dark:#85E89D">span</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> onmouseover</span><span style="color:#24292E;--shiki-dark:#E1E4E8">=</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"</span><span style="color:#6F42C1;--shiki-dark:#B392F0">alert</span><span style="color:#032F62;--shiki-dark:#9ECBFF">('hover')"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">&gt;Najedź mnie&lt;/</span><span style="color:#22863A;--shiki-dark:#85E89D">span</span><span style="color:#24292E;--shiki-dark:#E1E4E8">&gt; </span><span style="color:#6A737D;--shiki-dark:#6A737D">&lt;!-- 3 --&gt;</span></span>
<span class="line"></span></code></pre>

			</div>
		</figure><p>Powyższy kod HTML po odpaleniu w przeglądarce wyświetli trzy wyskakujące okienka:</p>
<ol>
<li>po wykonaniu skryptu z elementu <code>script</code>,</li>
<li>po błędzie wczytywania obrazka,</li>
<li>po najechaniu na tekst <q>Najedź mnie</q>.</li>
</ol>
<p>Po przepuszczeniu tego kodu przez DOMPurify dostaniemy:</p>
<figure class="code" typeof="SoftwareSourceCode">
			<figcaption class="code__caption">
				<span class="code__title" property="programmingLanguage">HTML</span>
				
			</figcaption>
			<div class="code__code" translate="no" property="text">
				<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">&lt;</span><span style="color:#22863A;--shiki-dark:#85E89D">img</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> src</span><span style="color:#24292E;--shiki-dark:#E1E4E8">=</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"x"</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> alt</span><span style="color:#24292E;--shiki-dark:#E1E4E8">=</span><span style="color:#032F62;--shiki-dark:#9ECBFF">""</span><span style="color:#24292E;--shiki-dark:#E1E4E8">&gt; </span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">&lt;</span><span style="color:#22863A;--shiki-dark:#85E89D">span</span><span style="color:#24292E;--shiki-dark:#E1E4E8">&gt;Najedź mnie&lt;/</span><span style="color:#22863A;--shiki-dark:#85E89D">span</span><span style="color:#24292E;--shiki-dark:#E1E4E8">&gt; </span></span>
<span class="line"></span></code></pre>

			</div>
		</figure><p>Wszystkie JS-owe elementy kodu zostały usunięte, tym samym zostawiając wyłącznie bezpieczny HTML.</p>
<h2 id="html-sanitizer-api--nowy-obrońca-sieci"><a class="header-anchor" href="https://blog.comandeer.pl/dezynfekcja#html-sanitizer-api--nowy-obrońca-sieci">HTML Sanitizer API – nowy obrońca Sieci</a></h2>
<p>W końcu doczekaliśmy się API chroniącego przed XSS-em, które jest wbudowane w przeglądarkę – <a href="https://developer.mozilla.org/en-US/docs/Web/API/HTML_Sanitizer_API" rel="noreferrer noopener">HTML Sanitizer API (API Dezynfekujące HTML-a)</a>. Składają się na niego dwa główne elementy:</p>
<ol>
<li><a href="https://developer.mozilla.org/en-US/docs/Web/API/Element/setHTML" rel="noreferrer noopener">metoda <code>#setHTML()</code></a>, dołączona do elementów HTML i <a href="https://developer.mozilla.org/en-US/docs/Web/API/ShadowRoot" rel="noreferrer noopener">korzeni Shadow DOM</a>,</li>
<li><a href="https://developer.mozilla.org/en-US/docs/Web/API/Document/parseHTML_static" rel="noreferrer noopener">statyczna metoda <code>Document.parseHTML()</code></a>.</li>
</ol>
<h3 id="bezpieczny-zaułek"><a class="header-anchor" href="https://blog.comandeer.pl/dezynfekcja#bezpieczny-zaułek">Bezpieczny zaułek</a></h3>
<p>Metoda <code>#setHTML()</code> jest odpowiednikiem <a href="https://developer.mozilla.org/en-US/docs/Web/API/Element/innerHTML" rel="noreferrer noopener">własności <code>#innerHTML</code></a>. Główna różnica między nimi polega na tym, że nowa metoda wyrzuca z przekazanego jej kodu HTML wszystkie niebezpieczne elementy:</p>
<figure class="code" typeof="SoftwareSourceCode">
			<figcaption class="code__caption">
				<span class="code__title" property="programmingLanguage">JavaScript</span>
				
			</figcaption>
			<div class="code__code" translate="no" property="text">
				<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">document.body.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">setHTML</span><span style="color:#24292E;--shiki-dark:#E1E4E8">( </span><span style="color:#032F62;--shiki-dark:#9ECBFF">`&lt;script&gt;alert( 'script' );&lt;/script&gt;</span></span>
<span class="line"><span style="color:#032F62;--shiki-dark:#9ECBFF">&lt;img src="x" alt="" onerror="alert('obrazek')"&gt;</span></span>
<span class="line"><span style="color:#032F62;--shiki-dark:#9ECBFF">&lt;span onmouseover="alert('hover')"&gt;Najedź mnie&lt;/span&gt;`</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> );</span></span>
<span class="line"></span></code></pre>

			</div>
		</figure><p>Wykonanie tego kodu sprawi, że zawartość strony zostanie podmieniona na następującą:</p>
<figure class="code" typeof="SoftwareSourceCode">
			<figcaption class="code__caption">
				<span class="code__title" property="programmingLanguage">HTML</span>
				
			</figcaption>
			<div class="code__code" translate="no" property="text">
				<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">&lt;</span><span style="color:#22863A;--shiki-dark:#85E89D">span</span><span style="color:#24292E;--shiki-dark:#E1E4E8">&gt;Najedź mnie&lt;/</span><span style="color:#22863A;--shiki-dark:#85E89D">span</span><span style="color:#24292E;--shiki-dark:#E1E4E8">&gt;</span></span>
<span class="line"></span></code></pre>

			</div>
		</figure><p>Jak widać, natywne API jest nawet dokładniejsze niż domyślne ustawienia biblioteki DOMPurify. Zniknął bowiem cały element <code>img</code>. Gdy przyjrzymy się <a href="https://wicg.github.io/sanitizer-api/#sanitization" rel="noreferrer noopener">algorytmowi dezynfekcji HTML-a</a> w specyfikacji, zauważymy, że jednym z kroków jest usuwanie elementów, które nie występują w konfiguracji dezynfekcji. Jeśli nie podamy własnego obiektu z opcjami, wówczas zostanie użyty <a href="https://wicg.github.io/sanitizer-api/#built-in-safe-default-configuration" rel="noreferrer noopener">domyślny</a> – a ten nie zawiera elementu <code>img</code>. Stąd nie ma go też w wynikowym HTML-u. Teoretycznie, żeby zachować obrazek, jako drugi argument metody <code>#setHTML()</code> trzeba podać <a href="https://developer.mozilla.org/en-US/docs/Web/API/SanitizerConfig" rel="noreferrer noopener">obiekt konfiguracyjny</a> – ale na ten moment nie działa to w żadnej przeglądarce.</p>
<p>Z kolei metoda statyczna <code>Document.parseHTML()</code> jest odpowiednikiem <a href="https://developer.mozilla.org/en-US/docs/Web/API/DOMParser" rel="noreferrer noopener">wbudowanego parsera HTML</a>. Metoda parsuje kod HTML, czyści go ze wszystkich niebezpiecznych fragmentów, a następnie tworzy nowy <a href="https://developer.mozilla.org/en-US/docs/Web/API/Document" rel="noreferrer noopener">obiekt <code>Document</code></a> na jego podstawie:</p>
<figure class="code" typeof="SoftwareSourceCode">
			<figcaption class="code__caption">
				<span class="code__title" property="programmingLanguage">JavaScript</span>
				
			</figcaption>
			<div class="code__code" translate="no" property="text">
				<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">console.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">log</span><span style="color:#24292E;--shiki-dark:#E1E4E8">( Document.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">parseHTML</span><span style="color:#24292E;--shiki-dark:#E1E4E8">( </span><span style="color:#032F62;--shiki-dark:#9ECBFF">`&lt;!DOCTYPE html&gt;</span></span>
<span class="line"><span style="color:#032F62;--shiki-dark:#9ECBFF">    &lt;html lang="pl"&gt;</span></span>
<span class="line"><span style="color:#032F62;--shiki-dark:#9ECBFF">        &lt;head&gt;</span></span>
<span class="line"><span style="color:#032F62;--shiki-dark:#9ECBFF">            &lt;title&gt;Testowy dokument&lt;/title&gt;</span></span>
<span class="line"><span style="color:#032F62;--shiki-dark:#9ECBFF">        &lt;/head&gt;</span></span>
<span class="line"><span style="color:#032F62;--shiki-dark:#9ECBFF">        &lt;body&gt;</span></span>
<span class="line"><span style="color:#032F62;--shiki-dark:#9ECBFF">            &lt;script&gt;alert( 'script' );&lt;/script&gt;</span></span>
<span class="line"><span style="color:#032F62;--shiki-dark:#9ECBFF">            &lt;h1&gt;Hello, world!&lt;/h1&gt;</span></span>
<span class="line"><span style="color:#032F62;--shiki-dark:#9ECBFF">        &lt;/body&gt;</span></span>
<span class="line"><span style="color:#032F62;--shiki-dark:#9ECBFF">    &lt;/html&gt;`</span><span style="color:#24292E;--shiki-dark:#E1E4E8">) );</span></span>
<span class="line"></span></code></pre>

			</div>
		</figure><p>Ten kod wyświetli w konsoli następujący dokument:</p>
<figure class="code" typeof="SoftwareSourceCode">
			<figcaption class="code__caption">
				<span class="code__title" property="programmingLanguage">HTML</span>
				
			</figcaption>
			<div class="code__code" translate="no" property="text">
				<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">&lt;!</span><span style="color:#22863A;--shiki-dark:#85E89D">DOCTYPE</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> html</span><span style="color:#24292E;--shiki-dark:#E1E4E8">&gt;</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">&lt;</span><span style="color:#22863A;--shiki-dark:#85E89D">html</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> lang</span><span style="color:#24292E;--shiki-dark:#E1E4E8">=</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"pl"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">&gt;</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">	&lt;</span><span style="color:#22863A;--shiki-dark:#85E89D">head</span><span style="color:#24292E;--shiki-dark:#E1E4E8">&gt;</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">		&lt;</span><span style="color:#22863A;--shiki-dark:#85E89D">title</span><span style="color:#24292E;--shiki-dark:#E1E4E8">&gt;Testowy dokument&lt;/</span><span style="color:#22863A;--shiki-dark:#85E89D">title</span><span style="color:#24292E;--shiki-dark:#E1E4E8">&gt;</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">	&lt;/</span><span style="color:#22863A;--shiki-dark:#85E89D">head</span><span style="color:#24292E;--shiki-dark:#E1E4E8">&gt;</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">	&lt;</span><span style="color:#22863A;--shiki-dark:#85E89D">body</span><span style="color:#24292E;--shiki-dark:#E1E4E8">&gt;</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">		&lt;</span><span style="color:#22863A;--shiki-dark:#85E89D">h1</span><span style="color:#24292E;--shiki-dark:#E1E4E8">&gt;Hello, world!&lt;/</span><span style="color:#22863A;--shiki-dark:#85E89D">h1</span><span style="color:#24292E;--shiki-dark:#E1E4E8">&gt;</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">	&lt;/</span><span style="color:#22863A;--shiki-dark:#85E89D">body</span><span style="color:#24292E;--shiki-dark:#E1E4E8">&gt;</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">&lt;/</span><span style="color:#22863A;--shiki-dark:#85E89D">html</span><span style="color:#24292E;--shiki-dark:#E1E4E8">&gt;</span></span>
<span class="line"></span></code></pre>

			</div>
		</figure><p>Jak widać, dokument jest praktycznie identyczny jak ten, który przekazaliśmy metodzie <code>Document.parseHTML()</code>. Jedyną&nbsp;różnicą jest brak elementu <code>script</code>.</p>
<h3 id="niebezpieczna-manipulacja"><a class="header-anchor" href="https://blog.comandeer.pl/dezynfekcja#niebezpieczna-manipulacja">Niebezpieczna manipulacja</a></h3>
<p>Warto wspomnieć, że zarówno <code>#setHTML()</code>, jak i <code>Document.parseHTML()</code>, mają swoje “niebezpieczne” odpowiedniki – <a href="https://developer.mozilla.org/en-US/docs/Web/API/Element/setHTMLUnsafe" rel="noreferrer noopener"><code>#setHTMLUnsafe()</code></a> oraz <a href="https://developer.mozilla.org/en-US/docs/Web/API/Document/parseHTMLUnsafe_static" rel="noreferrer noopener"><code>Document.parseHTMLUnsafe()</code></a>. Działają tak samo, ale nie usuwają niczego.</p>
<p>Tym samym, jeśli użyjemy <code>#setHTMLUnsafe()</code> na naszym testowym kodzie HTML:</p>
<figure class="code" typeof="SoftwareSourceCode">
			<figcaption class="code__caption">
				<span class="code__title" property="programmingLanguage">JavaScript</span>
				
			</figcaption>
			<div class="code__code" translate="no" property="text">
				<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">document.body.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">setHTMLUnsafe</span><span style="color:#24292E;--shiki-dark:#E1E4E8">( </span><span style="color:#032F62;--shiki-dark:#9ECBFF">`&lt;script&gt;alert( 'script' );&lt;/script&gt;</span></span>
<span class="line"><span style="color:#032F62;--shiki-dark:#9ECBFF">&lt;img src="x" alt="" onerror="alert('obrazek')"&gt;</span></span>
<span class="line"><span style="color:#032F62;--shiki-dark:#9ECBFF">&lt;span onmouseover="alert('hover')"&gt;Najedź mnie&lt;/span&gt;`</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> );</span></span>
<span class="line"></span></code></pre>

			</div>
		</figure><p>do zawartości strony trafi dokładnie ten kod HTML, bez żadnego czyszczenia:</p>
<figure class="code" typeof="SoftwareSourceCode">
			<figcaption class="code__caption">
				<span class="code__title" property="programmingLanguage">HTML</span>
				
			</figcaption>
			<div class="code__code" translate="no" property="text">
				<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">&lt;</span><span style="color:#22863A;--shiki-dark:#85E89D">script</span><span style="color:#24292E;--shiki-dark:#E1E4E8">&gt;</span><span style="color:#6F42C1;--shiki-dark:#B392F0">alert</span><span style="color:#24292E;--shiki-dark:#E1E4E8">( </span><span style="color:#032F62;--shiki-dark:#9ECBFF">'script'</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> );&lt;/</span><span style="color:#22863A;--shiki-dark:#85E89D">script</span><span style="color:#24292E;--shiki-dark:#E1E4E8">&gt;</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">&lt;</span><span style="color:#22863A;--shiki-dark:#85E89D">img</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> src</span><span style="color:#24292E;--shiki-dark:#E1E4E8">=</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"x"</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> alt</span><span style="color:#24292E;--shiki-dark:#E1E4E8">=</span><span style="color:#032F62;--shiki-dark:#9ECBFF">""</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> onerror</span><span style="color:#24292E;--shiki-dark:#E1E4E8">=</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"</span><span style="color:#6F42C1;--shiki-dark:#B392F0">alert</span><span style="color:#032F62;--shiki-dark:#9ECBFF">('obrazek')"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">&gt;</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">&lt;</span><span style="color:#22863A;--shiki-dark:#85E89D">span</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> onmouseover</span><span style="color:#24292E;--shiki-dark:#E1E4E8">=</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"</span><span style="color:#6F42C1;--shiki-dark:#B392F0">alert</span><span style="color:#032F62;--shiki-dark:#9ECBFF">('hover')"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">&gt;Najedź mnie&lt;/</span><span style="color:#22863A;--shiki-dark:#85E89D">span</span><span style="color:#24292E;--shiki-dark:#E1E4E8">&gt;</span></span>
<span class="line"></span></code></pre>

			</div>
		</figure><p>Podobnie stanie się, jeśli podmienimy <code>Document.parseHTML()</code> na <code>Document.parseHTMLUnsafe()</code>:</p>
<figure class="code" typeof="SoftwareSourceCode">
			<figcaption class="code__caption">
				<span class="code__title" property="programmingLanguage">JavaScript</span>
				
			</figcaption>
			<div class="code__code" translate="no" property="text">
				<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">console.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">log</span><span style="color:#24292E;--shiki-dark:#E1E4E8">( Document.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">parseHTMLUnsafe</span><span style="color:#24292E;--shiki-dark:#E1E4E8">( </span><span style="color:#032F62;--shiki-dark:#9ECBFF">`&lt;!DOCTYPE html&gt;</span></span>
<span class="line"><span style="color:#032F62;--shiki-dark:#9ECBFF">    &lt;html lang="pl"&gt;</span></span>
<span class="line"><span style="color:#032F62;--shiki-dark:#9ECBFF">        &lt;head&gt;</span></span>
<span class="line"><span style="color:#032F62;--shiki-dark:#9ECBFF">            &lt;title&gt;Testowy dokument&lt;/title&gt;</span></span>
<span class="line"><span style="color:#032F62;--shiki-dark:#9ECBFF">        &lt;/head&gt;</span></span>
<span class="line"><span style="color:#032F62;--shiki-dark:#9ECBFF">        &lt;body&gt;</span></span>
<span class="line"><span style="color:#032F62;--shiki-dark:#9ECBFF">            &lt;script&gt;alert( 'script' );&lt;/script&gt;</span></span>
<span class="line"><span style="color:#032F62;--shiki-dark:#9ECBFF">            &lt;h1&gt;Hello, world!&lt;/h1&gt;</span></span>
<span class="line"><span style="color:#032F62;--shiki-dark:#9ECBFF">        &lt;/body&gt;</span></span>
<span class="line"><span style="color:#032F62;--shiki-dark:#9ECBFF">    &lt;/html&gt;`</span><span style="color:#24292E;--shiki-dark:#E1E4E8">) );</span></span>
<span class="line"></span></code></pre>

			</div>
		</figure><p>Ten kod stworzy dokładnie taki dokument HTML:</p>
<figure class="code" typeof="SoftwareSourceCode">
			<figcaption class="code__caption">
				<span class="code__title" property="programmingLanguage">HTML</span>
				
			</figcaption>
			<div class="code__code" translate="no" property="text">
				<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">&lt;!</span><span style="color:#22863A;--shiki-dark:#85E89D">DOCTYPE</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> html</span><span style="color:#24292E;--shiki-dark:#E1E4E8">&gt;</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">&lt;</span><span style="color:#22863A;--shiki-dark:#85E89D">html</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> lang</span><span style="color:#24292E;--shiki-dark:#E1E4E8">=</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"pl"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">&gt;</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">	&lt;</span><span style="color:#22863A;--shiki-dark:#85E89D">head</span><span style="color:#24292E;--shiki-dark:#E1E4E8">&gt;</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">		&lt;</span><span style="color:#22863A;--shiki-dark:#85E89D">title</span><span style="color:#24292E;--shiki-dark:#E1E4E8">&gt;Testowy dokument&lt;/</span><span style="color:#22863A;--shiki-dark:#85E89D">title</span><span style="color:#24292E;--shiki-dark:#E1E4E8">&gt;</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">	&lt;/</span><span style="color:#22863A;--shiki-dark:#85E89D">head</span><span style="color:#24292E;--shiki-dark:#E1E4E8">&gt;</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">	&lt;</span><span style="color:#22863A;--shiki-dark:#85E89D">body</span><span style="color:#24292E;--shiki-dark:#E1E4E8">&gt;</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">		&lt;</span><span style="color:#22863A;--shiki-dark:#85E89D">script</span><span style="color:#24292E;--shiki-dark:#E1E4E8">&gt;</span><span style="color:#6F42C1;--shiki-dark:#B392F0">alert</span><span style="color:#24292E;--shiki-dark:#E1E4E8">( </span><span style="color:#032F62;--shiki-dark:#9ECBFF">'script'</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> );&lt;/</span><span style="color:#22863A;--shiki-dark:#85E89D">script</span><span style="color:#24292E;--shiki-dark:#E1E4E8">&gt;</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">		&lt;</span><span style="color:#22863A;--shiki-dark:#85E89D">h1</span><span style="color:#24292E;--shiki-dark:#E1E4E8">&gt;Hello, world!&lt;/</span><span style="color:#22863A;--shiki-dark:#85E89D">h1</span><span style="color:#24292E;--shiki-dark:#E1E4E8">&gt;</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">	&lt;/</span><span style="color:#22863A;--shiki-dark:#85E89D">body</span><span style="color:#24292E;--shiki-dark:#E1E4E8">&gt;</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">&lt;/</span><span style="color:#22863A;--shiki-dark:#85E89D">html</span><span style="color:#24292E;--shiki-dark:#E1E4E8">&gt;</span></span>
<span class="line"></span></code></pre>

			</div>
		</figure><h3 id="kompatybilność"><a class="header-anchor" href="https://blog.comandeer.pl/dezynfekcja#kompatybilność">Kompatybilność</a></h3>
<p>Gdzie zatem można użyć nowego API? Otóż… nigdzie. Pojawić się powinno w <a href="https://caniuse.com/mdn-api_element_sethtml" rel="noreferrer noopener">Firefoksie 147</a>, którego wydanie planowane jest na 13 stycznia 2026 roku. W Chrome dostępne jest jako eksperymentalny ficzer, ukryty za <a href="https://developer.chrome.com/docs/web-platform/chrome-flags" rel="noreferrer noopener">flagą</a>. Na ten moment <a href="https://chromestatus.com/feature/5814067399491584" rel="noreferrer noopener">nie wiadomo</a>, kiedy trafi do Chrome’a jako stabilny ficzer. Na pewno nie pomaga temu fakt, że <a href="https://wicg.github.io/sanitizer-api/" rel="noreferrer noopener">specyfikacja</a> wciąż się zmienia i nie wyszła jeszcze z grupy inkubującej nowe standardy.</p>
<p>Zatem wygląda na to, że w najbliższej przyszłości rozwiązania pokroju DOMPurify wciąż będą jedynymi rozwiązaniami pozwalającymi dezynfekować kod HTML. Jednak przeglądarki mają w rękawie jeszcze jednego asa… Ale to już opowieść na inną okazję!</p>
]]></content>
		</entry>
	
		<entry>
			<title type="html">Interesujący dyngs</title>
			
				<author>
					<name>Comandeer</name>
				</author>
			
			<link href="https://blog.comandeer.pl/interesujacy-dyngs" rel="alternate" type="text/html"/>
			<published>2025-12-15T23:00:00.000Z</published>
			<updated>2025-12-15T23:00:00.000Z</updated>
			<id>https://blog.comandeer.pl/interesujacy-dyngs</id>
			
				<summary><![CDATA[Krótki poradnik, jak wyrazić zainteresowanie elementem UI?]]></summary>
			
			<content type="html"><![CDATA[<p>Zwrot deklaratywny trwa w najlepsze. Oprócz <a href="https://blog.comandeer.pl/rozkazuje-ci">obsługi komend</a>, ostatnio pojawiło się też <a href="https://developer.mozilla.org/en-US/docs/Web/API/Popover_API/Using_interest_invokers" rel="noreferrer noopener"><i lang="en">Interest Invoker API</i> (API Wyzwalacza Zainteresowania)</a>.</p>
<h2 id="zainteresowanie"><a class="header-anchor" href="https://blog.comandeer.pl/interesujacy-dyngs#zainteresowanie">Zainteresowanie?</a></h2>
<p>Wypada zacząć od wyjaśnienia, czym jest <q>zainteresowanie</q> w tym kontekście. W <a href="https://open-ui.org/components/interest-invokers.explainer/#introduction" rel="noreferrer noopener">oficjalnej propozycji nowego API</a> jest to wyjaśnione mocno enigmatycznie:</p>
<blockquote>
<p lang="en">[…] rather than being activated via a <strong>click</strong> on the element, this API uses a lighter-touch way for the user to “show interest” in an element without fully activating it.</p>
<p>[[…] zamiast aktywowania poprzez <strong>klik</strong> na elemencie, to API używa lżejszego sposobu okazania przez osobę użytkowniczą “zainteresowania” elementem bez jego pełnej aktywacji.]</p>
</blockquote>
<p>Taka definicja nie wyjaśnia za dużo, ale na szczęście jest <a href="https://open-ui.org/components/interest-invokers.explainer/#hids-and-interest" rel="noreferrer noopener">cała sekcja</a> poświęcona różnym sposobom interakcji z urządzeniem i jak się to przekłada na zainteresowanie:</p>
<table>
<thead>
<tr>
<th>Sposób interakcji z urządzeniem</th>
<th>Sposób okazywania zainteresowania</th>
<th>Sposób tracenia zainteresowania</th>
</tr>
</thead>
<tbody>
<tr>
<td><a href="https://open-ui.org/components/interest-invokers.explainer/#mouse" rel="noreferrer noopener">Myszka</a></td>
<td>Najechanie kursorem myszy (hover) i przytrzymanie go na elemencie przez pewien czas</td>
<td>Zjechanie kursorem myszy z elementu</td>
</tr>
<tr>
<td><a href="https://open-ui.org/components/interest-invokers.explainer/#keyboard" rel="noreferrer noopener">Klawiatura</a></td>
<td>Sfocusowanie elementu i pozostawienie go sfocusowanym przez pewien czas</td>
<td>Zabranie focusu z elementu, naciśnięcie <kbd>Esc</kbd></td>
</tr>
<tr>
<td><a href="https://open-ui.org/components/interest-invokers.explainer/#touchscreen" rel="noreferrer noopener">Ekran dotykowy</a></td>
<td>Gest <a href="https://en.wikipedia.org/wiki/Pointing_device_gesture#:~:text=0%3A04-,Long%20Press,-Duration%3A%205%20seconds" rel="noreferrer noopener">długiego naciśnięcia (<i lang="en">long press</i>)</a></td>
<td>Dotknięcie poza elementem</td>
</tr>
<tr>
<td><a href="https://open-ui.org/components/interest-invokers.explainer/#other" rel="noreferrer noopener">Pozostałe</a></td>
<td>Przeglądarka dobiera odpowiedni sposób</td>
<td>Przeglądarka dobiera odpowiedni sposób</td>
</tr>
</tbody>
</table>
<p>Innymi słowy: “okazywanie zainteresowania” to ulepszony hover. Działa bowiem nie tylko z myszką, dostosowuje się także do innych urządzeń wejściowych i sposobów interakcji z urządzeniem.</p>
<h2 id="nowe-api"><a class="header-anchor" href="https://blog.comandeer.pl/interesujacy-dyngs#nowe-api">Nowe API</a></h2>
<div class="note" role="note" aria-labelledby="note-10"><p class="note__label" id="note-10">Dygresja</p><div class="note__content"><p>Dla uproszczenia, będę opisywał API z perspektywy osoby korzystającej z myszy. Niemniej wszędzie tam, gdzie pojawia się najechanie myszą, tak naprawdę chodzi o wszystkie wymienione wyżej sposoby interakcji.</p></div></div>
<h3 id="tooltipy"><a class="header-anchor" href="https://blog.comandeer.pl/interesujacy-dyngs#tooltipy">Tooltipy</a></h3>
<p>Nowe API jest podobne do istniejącego <a href="https://developer.mozilla.org/en-US/docs/Web/API/Invoker_Commands_API" rel="noreferrer noopener">Invoker Commands API</a>. Jego głównym elementem jest <a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/a#interestfor" rel="noreferrer noopener">atrybut <code>[interestfor]</code></a>. Można go dodać do elementów <a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/a" rel="noreferrer noopener"><code>a</code></a>, <a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/area" rel="noreferrer noopener"><code>area</code></a>, <a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/button" rel="noreferrer noopener"><code>button</code></a> oraz <a href="https://developer.mozilla.org/en-US/docs/Web/SVG/Reference/Element/a" rel="noreferrer noopener"><code>a</code> w SVG</a>. Wskazuje on na element, z którym coś ma się stać, gdy elementowi z tym atrybutem okaże się&nbsp;zainteresowanie. Sztandarowym przykładem jest pokazanie tooltipa:</p>
<div><embed- src="https://codepen.io/Comandeer/pen/xbVNNYJ"><p class="embed-fallback"><a href="https://codepen.io/Comandeer/pen/xbVNNYJ" rel="noreferrer noopener">Przejdź bezpośrednio do osadzonej treści na CodePenie.</a></p></embed-></div>
<figure class="code" typeof="SoftwareSourceCode">
			<figcaption class="code__caption">
				<span class="code__title" property="programmingLanguage">HTML</span>
				
			</figcaption>
			<div class="code__code" translate="no" property="text">
				<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">&lt;</span><span style="color:#22863A;--shiki-dark:#85E89D">button</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> interestfor</span><span style="color:#24292E;--shiki-dark:#E1E4E8">=</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"tooltip"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">&gt;Jakiś przycisk&lt;/</span><span style="color:#22863A;--shiki-dark:#85E89D">button</span><span style="color:#24292E;--shiki-dark:#E1E4E8">&gt; </span><span style="color:#6A737D;--shiki-dark:#6A737D">&lt;!-- 1 --&gt;</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">&lt;</span><span style="color:#22863A;--shiki-dark:#85E89D">span</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> popover</span><span style="color:#24292E;--shiki-dark:#E1E4E8">=</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"hint"</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> id</span><span style="color:#24292E;--shiki-dark:#E1E4E8">=</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"tooltip"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">&gt;Jakiś tooltip&lt;/</span><span style="color:#22863A;--shiki-dark:#85E89D">span</span><span style="color:#24292E;--shiki-dark:#E1E4E8">&gt; </span><span style="color:#6A737D;--shiki-dark:#6A737D">&lt;!-- 2 --&gt;</span></span>
<span class="line"></span></code></pre>

			</div>
		</figure><p>Przycisk ma atrybut <code>[interestfor]</code>, wskazujący na element <code>#tooltip</code> (1). Z kolei element <code>#tooltip</code> ma <a href="https://developer.mozilla.org/en-US/docs/Web/API/Popover_API/Using#using_hint_popover_state" rel="noreferrer noopener">atrybut <code>[popover=hint]</code></a> (2). Dzięki temu po najechaniu myszą na przycisk, po chwili powinien pod nim pojawić się tooltip:</p>
<figure class="figure"><a class="figure__link" href="/assets/images/interesujacy-dyngs/tooltip-128w.avif"><picture><source type="image/avif" srcset="/assets/images/interesujacy-dyngs/tooltip-128w.avif 128w" sizes="90vw"><source type="image/webp" srcset="/assets/images/interesujacy-dyngs/tooltip-128w.webp 128w" sizes="90vw"><source type="image/png" srcset="/assets/images/interesujacy-dyngs/tooltip-128w.png 128w" sizes="90vw"><img src="/assets/images/interesujacy-dyngs/tooltip-128w.png" width="128" height="71" style="aspect-ratio: 128 / 71" alt="Przycisk &quot;Jakiś przycisk&quot;, pod którym znajduje się obramowane okienko z napisem &quot;Jakiś tooltip&quot;." loading="lazy" decoding="async"></picture></a><figcaption class="figure__caption"><p>Kliknij obrazek, aby go powiększyć</p></figcaption></figure>
<p>Natomiast sama wartość <code>hint</code> dla atrybutu <code>[popover]</code> informuje przeglądarkę, że ma traktować ten popover jako pomocniczy. Dzięki temu jego pojawienie się będzie zamykać jedynie inne pomocnicze popovery. Pozostałe będą go ignorować.</p>
<p>Położenie tooltipa można kontrolować przy pomocy <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Properties/position-area" rel="noreferrer noopener">własności CSS <code>position-area</code></a>:</p>
<figure class="code" typeof="SoftwareSourceCode">
			<figcaption class="code__caption">
				<span class="code__title" property="programmingLanguage">CSS</span>
				
			</figcaption>
			<div class="code__code" translate="no" property="text">
				<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">[</span><span style="color:#6F42C1;--shiki-dark:#B392F0">popover</span><span style="color:#D73A49;--shiki-dark:#F97583">=</span><span style="color:#032F62;--shiki-dark:#9ECBFF">hint</span><span style="color:#24292E;--shiki-dark:#E1E4E8">] {</span></span>
<span class="line"><span style="color:#005CC5;--shiki-dark:#79B8FF">	position-area</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#005CC5;--shiki-dark:#79B8FF">bottom</span><span style="color:#24292E;--shiki-dark:#E1E4E8">; </span><span style="color:#6A737D;--shiki-dark:#6A737D">/* 1 */</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">}</span></span>
<span class="line"></span></code></pre>

			</div>
		</figure><p>W naszym przykładzie użyliśmy wartości <code>bottom</code> (1), dzięki czemu tooltip pojawia się pod przyciskiem.</p>
<p>CSS pozwala także zmienić opóźnienie po interakcji z elementem, po którym przeglądarka uzna, że ktoś wyraża zainteresowanie:</p>
<figure class="code" typeof="SoftwareSourceCode">
			<figcaption class="code__caption">
				<span class="code__title" property="programmingLanguage">CSS</span>
				
			</figcaption>
			<div class="code__code" translate="no" property="text">
				<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">[</span><span style="color:#6F42C1;--shiki-dark:#B392F0">interestfor</span><span style="color:#24292E;--shiki-dark:#E1E4E8">] { </span><span style="color:#6A737D;--shiki-dark:#6A737D">/* 2 */</span></span>
<span class="line"><span style="color:#005CC5;--shiki-dark:#79B8FF">	interest-delay</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#005CC5;--shiki-dark:#79B8FF">0</span><span style="color:#D73A49;--shiki-dark:#F97583">s</span><span style="color:#24292E;--shiki-dark:#E1E4E8">; </span><span style="color:#6A737D;--shiki-dark:#6A737D">/* 1 */</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">}</span></span>
<span class="line"></span></code></pre>

			</div>
		</figure><p>W powyższym przykładzie <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Properties/interest-delay" rel="noreferrer noopener">ustawiliśmy opóźnienie</a> na <code>0s</code> (1). Tym samym będzie się to zachowywać praktycznie identycznie do pseudoklas <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Selectors/:hover" rel="noreferrer noopener"><code>:hover</code></a> i <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Selectors/:focus" rel="noreferrer noopener"><code>:focus</code></a>. Warto też zauważyć, że własność tę należy dodać do elementu z atrybutem <code>[interestfor]</code>, nie zaś do popovera (2).</p>
<h3 id="zaawansowane-interakcje"><a class="header-anchor" href="https://blog.comandeer.pl/interesujacy-dyngs#zaawansowane-interakcje">Zaawansowane interakcje</a></h3>
<p>Można jednak tworzyć o wiele bardziej skomplikowane interakcje, niż pokazywanie tooltipa:</p>
<div><embed- src="https://codepen.io/Comandeer/pen/OPNYYqN"><p class="embed-fallback"><a href="https://codepen.io/Comandeer/pen/OPNYYqN" rel="noreferrer noopener">Przejdź bezpośrednio do osadzonej treści na CodePenie.</a></p></embed-></div>
<p>W powyższym przykładzie najechanie na przycisk sprawi trzy rzeczy:</p>
<ol>
<li>najechany przycisk dostanie pomarańczowe tło,</li>
<li>kółko na środku strony zmieni kształt i kolor obramowania,</li>
<li>kółko na środku dostanie kolor tła równy zawartości atrybutu <code>[data-color]</code> najechanego przycisku.</li>
</ol>
<p>Kod HTML wygląda następująco:</p>
<figure class="code" typeof="SoftwareSourceCode">
			<figcaption class="code__caption">
				<span class="code__title" property="programmingLanguage">HTML</span>
				
			</figcaption>
			<div class="code__code" translate="no" property="text">
				<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">&lt;</span><span style="color:#22863A;--shiki-dark:#85E89D">p</span><span style="color:#24292E;--shiki-dark:#E1E4E8">&gt;</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">	&lt;</span><span style="color:#22863A;--shiki-dark:#85E89D">button</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> interestfor</span><span style="color:#24292E;--shiki-dark:#E1E4E8">=</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"circle"</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> data-color</span><span style="color:#24292E;--shiki-dark:#E1E4E8">=</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"pink"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">&gt;Różowy&lt;/</span><span style="color:#22863A;--shiki-dark:#85E89D">button</span><span style="color:#24292E;--shiki-dark:#E1E4E8">&gt; </span><span style="color:#6A737D;--shiki-dark:#6A737D">&lt;!-- 1 --&gt;</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">	&lt;</span><span style="color:#22863A;--shiki-dark:#85E89D">button</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> interestfor</span><span style="color:#24292E;--shiki-dark:#E1E4E8">=</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"circle"</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> data-color</span><span style="color:#24292E;--shiki-dark:#E1E4E8">=</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"blue"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">&gt;Niebieski&lt;/</span><span style="color:#22863A;--shiki-dark:#85E89D">button</span><span style="color:#24292E;--shiki-dark:#E1E4E8">&gt; </span><span style="color:#6A737D;--shiki-dark:#6A737D">&lt;!-- 2 --&gt;</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">	&lt;</span><span style="color:#22863A;--shiki-dark:#85E89D">button</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> interestfor</span><span style="color:#24292E;--shiki-dark:#E1E4E8">=</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"circle"</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> data-color</span><span style="color:#24292E;--shiki-dark:#E1E4E8">=</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"green"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">&gt;Zielony&lt;/</span><span style="color:#22863A;--shiki-dark:#85E89D">button</span><span style="color:#24292E;--shiki-dark:#E1E4E8">&gt; </span><span style="color:#6A737D;--shiki-dark:#6A737D">&lt;!-- 3 --&gt;</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">&lt;/</span><span style="color:#22863A;--shiki-dark:#85E89D">p</span><span style="color:#24292E;--shiki-dark:#E1E4E8">&gt;</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">&lt;</span><span style="color:#22863A;--shiki-dark:#85E89D">div</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> class</span><span style="color:#24292E;--shiki-dark:#E1E4E8">=</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"circle"</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> id</span><span style="color:#24292E;--shiki-dark:#E1E4E8">=</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"circle"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">&gt;&lt;/</span><span style="color:#22863A;--shiki-dark:#85E89D">div</span><span style="color:#24292E;--shiki-dark:#E1E4E8">&gt; </span><span style="color:#6A737D;--shiki-dark:#6A737D">&lt;!-- 4 --&gt;</span></span>
<span class="line"></span></code></pre>

			</div>
		</figure><p>Każdy z przycisków (1, 2, 3) ma atrybut <code>[interestfor]</code> wskazujący na element <code>#circle</code> (4).</p>
<p>Z kolei kod CSS odpowiedzialny za zmianę tła przycisków oraz obramowania koła prezentuje się tak:</p>
<figure class="code" typeof="SoftwareSourceCode">
			<figcaption class="code__caption">
				<span class="code__title" property="programmingLanguage">CSS</span>
				
			</figcaption>
			<div class="code__code" translate="no" property="text">
				<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">:</span><span style="color:#22863A;--shiki-dark:#85E89D">interest-source</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> { </span><span style="color:#6A737D;--shiki-dark:#6A737D">/* 1 */</span></span>
<span class="line"><span style="color:#005CC5;--shiki-dark:#79B8FF">	background</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#005CC5;--shiki-dark:#79B8FF">orange</span><span style="color:#24292E;--shiki-dark:#E1E4E8">; </span><span style="color:#6A737D;--shiki-dark:#6A737D">/* 2 */</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">}</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">:</span><span style="color:#22863A;--shiki-dark:#85E89D">interest-target</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> { </span><span style="color:#6A737D;--shiki-dark:#6A737D">/* 3 */</span></span>
<span class="line"><span style="color:#005CC5;--shiki-dark:#79B8FF">	border</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#005CC5;--shiki-dark:#79B8FF">2</span><span style="color:#D73A49;--shiki-dark:#F97583">px</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> #f00</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> dashed</span><span style="color:#24292E;--shiki-dark:#E1E4E8">; </span><span style="color:#6A737D;--shiki-dark:#6A737D">/* 4 */</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">}</span></span>
<span class="line"></span></code></pre>

			</div>
		</figure><p><a href="https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Selectors/:interest-source" rel="noreferrer noopener">Pseudoklasa <code>:interest-source</code></a> (1) wskazuje na aktualnie najechany elementy z atrybutem <code>[interestfor]</code>. Dostanie on pomarańczowe tło (2). Z kolei element wskazywany przez atrybut <code>[interestfor]</code> aktualnie najechanego elementu jest wskazywany przez <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Selectors/:interest-target" rel="noreferrer noopener">pseudoklasę <code>:interest-target</code></a> (3). Dostanie on czerwone, przerywane obramowanie (4).</p>
<p>Natomiast do zmiany tła kółka potrzebujemy kodu JS:</p>
<figure class="code" typeof="SoftwareSourceCode">
			<figcaption class="code__caption">
				<span class="code__title" property="programmingLanguage">JavaScript</span>
				
			</figcaption>
			<div class="code__code" translate="no" property="text">
				<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">const</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> circle</span><span style="color:#D73A49;--shiki-dark:#F97583"> =</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> document.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">querySelector</span><span style="color:#24292E;--shiki-dark:#E1E4E8">( </span><span style="color:#032F62;--shiki-dark:#9ECBFF">'#circle'</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> );</span></span>
<span class="line"></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">circle.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">addEventListener</span><span style="color:#24292E;--shiki-dark:#E1E4E8">( </span><span style="color:#032F62;--shiki-dark:#9ECBFF">'interest'</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, ( </span><span style="color:#E36209;--shiki-dark:#FFAB70">evt</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> ) </span><span style="color:#D73A49;--shiki-dark:#F97583">=&gt;</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> { </span><span style="color:#6A737D;--shiki-dark:#6A737D">// 1</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">	evt.target.style.backgroundColor </span><span style="color:#D73A49;--shiki-dark:#F97583">=</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> evt.source.dataset.color; </span><span style="color:#6A737D;--shiki-dark:#6A737D">// 2</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">} );</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">circle.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">addEventListener</span><span style="color:#24292E;--shiki-dark:#E1E4E8">( </span><span style="color:#032F62;--shiki-dark:#9ECBFF">'loseinterest'</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, ( </span><span style="color:#E36209;--shiki-dark:#FFAB70">evt</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> ) </span><span style="color:#D73A49;--shiki-dark:#F97583">=&gt;</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> { </span><span style="color:#6A737D;--shiki-dark:#6A737D">// 3</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">	evt.target.style.backgroundColor </span><span style="color:#D73A49;--shiki-dark:#F97583">=</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> 'transparent'</span><span style="color:#24292E;--shiki-dark:#E1E4E8">; </span><span style="color:#6A737D;--shiki-dark:#6A737D">// 4</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">} );</span></span>
<span class="line"></span></code></pre>

			</div>
		</figure><p>Gdy najedziemy na przycisk z atrybutem <code>[interestfor]</code>, na elemencie przez niego wskazywanym odpali się&nbsp;<a href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/interest_event" rel="noreferrer noopener">zdarzenie <code>interest</code></a> (1). Gdy zajdzie, do kółka przypisujemy kolor wskazywany przez atrybut <code>[data-color]</code> przycisku (2). Własność <code>evt.target</code> przechowuje element wskazywany przez atrybut <code>[interestfor]</code>, podczas gdy <code>evt-source</code> – element z atrybutem <code>[interestfor]</code>. Z kolei, gdy zjedzie się myszą z przycisku, wówczas na kółku zostanie odpalone <a href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/loseinterest_event" rel="noreferrer noopener">zdarzenie <code>loseinterest</code></a> (3). Wtedy też usuwamy mu tło (4).</p>
<h3 id="przyszłość"><a class="header-anchor" href="https://blog.comandeer.pl/interesujacy-dyngs#przyszłość">Przyszłość</a></h3>
<p>Nowe API ma zdecydowanie gorsze wsparcie niż swój klikalny odpowiednik. Na ten moment <a href="https://caniuse.com/wf-interest-invokers" rel="noreferrer noopener">działa wyłącznie w Chrome i Edge’u</a>. Co nie jest aż takie zaskakujące, jeśli weźmie się pod uwagę, że Interest Invoker API nie ma jeszcze nawet oficjalnej specyfikacji. To oznacza, że jeszcze trochę&nbsp;sobie poczekamy na w pełni natywne, deklaratywne tooltipy. A jest na co czekać, bo zrobienie ich dobrze, z dobrym pozycjonowaniem tooltipów, to jest zaskakująco skomplikowany problem. Istnieją całe biblioteki, takie jak <a href="https://floating-ui.com/" rel="noreferrer noopener">Floating UI</a>, których jedynym zadaniem jest obliczanie, w którym miejscu okienko z pomocną etykietą powinno się pokazać. Do tego trzeba dodać całą JS-ową logikę, która będzie pokazywać tooltipa po odpowiednio długim przytrzymaniu myszy na elemencie. A przecież trzeba jeszcze obsłużyć inne sposoby “okazywania zainteresowania”. To w gruncie rzeczy problem podobny do <a href="https://blog.comandeer.pl/sezamie-zamknij-sie">zamykania elementów UI zgodnie z konwencjami danej platformy</a>.</p>
<p>Dlatego ja z niecierpliwością czekam na czasy, gdy okazywanie zainteresowania w końcu stanie się łatwe.</p>
]]></content>
		</entry>
	
		<entry>
			<title type="html">Pudełko z ciasteczkami</title>
			
				<author>
					<name>Comandeer</name>
				</author>
			
			<link href="https://blog.comandeer.pl/pudelko-z-ciasteczkami" rel="alternate" type="text/html"/>
			<published>2025-12-14T23:00:00.000Z</published>
			<updated>2025-12-14T23:00:00.000Z</updated>
			<id>https://blog.comandeer.pl/pudelko-z-ciasteczkami</id>
			
				<summary><![CDATA[Ciasteczka są częścią platformy sieciowej od prawie zawsze. W końcu doczekały się sensownego API.]]></summary>
			
			<content type="html"><![CDATA[<p>Relacja Sieci z <a href="https://developer.mozilla.org/en-US/docs/Glossary/Cookie" rel="noreferrer noopener"><i lang="en">cookies</i> (ciasteczkami)</a> od prawie samego początku była mocno skomplikowana. Powstały jako wynalazek Netscape’a, żeby przechowywać krótkie informacje po stronie przeglądarki. Potem zaczęły być używane do śledzenia osób przeglądających Internet. W końcu pojawiły się <a href="https://www.cookiebot.com/en/cookie-law/" rel="noreferrer noopener">ciasteczkowe prawa</a> i dzisiaj praktycznie każda strona ma informację o wykorzystaniu ciasteczek. Niemniej nie tylko na polu prawno-etycznym ciasteczkom było trudno.</p>
<h2 id="podstawy-działania-ciasteczek-czyli-stary-przepis-babci"><a class="header-anchor" href="https://blog.comandeer.pl/pudelko-z-ciasteczkami#podstawy-działania-ciasteczek-czyli-stary-przepis-babci">Podstawy działania ciasteczek, czyli stary przepis babci</a></h2>
<p>Lata 90. to był dziwny czas dla platformy sieciowej. Jej kształt dopiero się wykuwał i sporo obecnie standardowych technologii wtedy nawet nie istniało. Same ciasteczka pojawiły się w 1994, więc na rok przed JavaScriptem! Z tego też względu <a href="https://web.archive.org/web/19961027104920/http://www.netscape.com/newsref/std/cookie_spec.html" rel="noreferrer noopener">oryginalna propozycja Netscape’a</a> z oczywistych względów nie wspomina o żadnym JS-owym API. Zamiast tego jedynym sposobem ustawienia ciasteczek był nagłówek HTTP <code>Set-Cookie</code>:</p>
<figure class="code" typeof="SoftwareSourceCode">
			<figcaption class="code__caption">
				<span class="code__title" property="programmingLanguage"></span>
				
			</figcaption>
			<div class="code__code" translate="no" property="text">
				<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span>Set-Cookie: NAME=VALUE; expires=DATE; path=PATH; domain=DOMAIN_NAME; secure</span></span>
<span class="line"><span></span></span></code></pre>

			</div>
		</figure><p>Jego poszczególne części to:</p>
<ol>
<li><code>NAME</code> – nazwa ciasteczka,</li>
<li><code>VALUE</code> – wartość ciasteczka</li>
<li><code>expires=DATE</code> – data ważności, po której ciasteczko zostanie usunięte; format daty opisany jest w <a href="https://datatracker.ietf.org/doc/html/rfc2616#section-3.3.1" rel="noreferrer noopener">standardzie RFC 2616</a>,</li>
<li><code>path=PATH</code> – <a href="https://developer.mozilla.org/en-US/docs/Web/URI/Reference/Path" rel="noreferrer noopener">ścieżka</a>, dla której ciasteczko ma być używane,</li>
<li><code>domain=DOMAIN_NAME</code> – nazwa domeny, dla której ciasteczko ma być używane,</li>
<li><code>secure</code> – słowo kluczowe, oznaczające, że ciasteczko ma być wysyłane tylko bezpiecznym połączeniem (HTTPS).</li>
</ol>
<p>Obowiązkowe było podanie tylko nazwy i wartości, resztę części można było ominąć.</p>
<p>Założenie ciasteczek było proste: serwer wysyła do przeglądarki jakąś informację (np. identyfikator koszyka w sklepie). Gdy dana strona zostanie potem ponownie odwiedzona, przeglądarka odsyła tę informację z powrotem. Robiła to przy pomocy nagłówka <code>Cookie</code>, dodawanego do żądania HTTP:</p>
<figure class="code" typeof="SoftwareSourceCode">
			<figcaption class="code__caption">
				<span class="code__title" property="programmingLanguage"></span>
				
			</figcaption>
			<div class="code__code" translate="no" property="text">
				<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span>Cookie: cookie1=wartosc; cookie2=wartosc; cookie3=wartosc</span></span>
<span class="line"><span></span></span></code></pre>

			</div>
		</figure><p>Przeglądarka wysyłała jedynie nazwy i wartości istniejących ciasteczek. Każda para &lt;nazwa, wartość&gt; była oddzielona od siebie średnikiem. Tym sposobem można powiązać konkretną osobę (przeglądarkę) z konkretnymi danymi po stronie serwera.</p>
<p>Co ciekawe, nagłówek HTTP <code>Set-Cookie</code> niemalże w niezmienionej postaci przetrwał do dziś. Oficjalna specyfikacja ciasteczek, <a href="https://datatracker.ietf.org/doc/html/rfc6265" rel="noreferrer noopener">RFC 6265</a>, <a href="https://datatracker.ietf.org/doc/html/rfc6265#section-4.1" rel="noreferrer noopener">definiuje go następująco</a>:</p>
<figure class="code" typeof="SoftwareSourceCode">
			<figcaption class="code__caption">
				<span class="code__title" property="programmingLanguage"></span>
				
			</figcaption>
			<div class="code__code" translate="no" property="text">
				<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span>Set-Cookie: NAME=VALUE; Expires=DATE; Max-Age=MAX_AGE; Domain=DOMAIN_NAME; Path=PATH; Secure; HttpOnly; EXTENSIONS</span></span>
<span class="line"><span></span></span></code></pre>

			</div>
		</figure><p>Tak naprawdę pojawiły się trzy nowe elementy:</p>
<ol>
<li><code>Max-Age=MAX_AGE</code> – liczba sekund, po której ciasteczko traci ważność,</li>
<li><code>HttpOnly</code> – słowo kluczowe wskazujące,&nbsp;że ciasteczko ma być dostępne wyłącznie w żądaniach i odpowiedziach HTTP, bez dostępu z poziomu JS-a,</li>
<li><code>EXTENSIONS</code> – dodatkowe elementy nagłówka, opisane w innych specyfikacjach.</li>
</ol>
<p>Na ten moment istnieją tak naprawdę dwa ustandaryzowane dodatkowe elementy nagłówka:</p>
<ol>
<li><code>Partitioned</code> – słowo kluczowe wskazujące, że ciasteczko ma zostać wrzucone do osobnego słoika, dzięki czemu nie będzie mogło być użyte do śledzenia przeglądarki między różnymi stronami; część tzw. <a href="https://developer.mozilla.org/en-US/docs/Web/Privacy/Guides/Privacy_sandbox/Partitioned_cookies" rel="noreferrer noopener">CHIPS (Cookies Having Independent Partitioned State, Ciasteczka Posiadające Niezależny Podzielony Stan)</a>,</li>
<li><code>SameSite=None|Lax|Strict</code> – określa, jak ciasteczko ma się&nbsp;<a href="https://web.dev/articles/samesite-cookies-explained" rel="noreferrer noopener">zachowywać w żądaniach pomiędzy różnymi stronami</a>.</li>
</ol>
<div class="note" role="note" aria-labelledby="note-7"><p class="note__label" id="note-7">Dygresja</p><div class="note__content"><p>Nie będę tutaj wchodził w szczegóły tych dwóch rozszerzeń, ponieważ zasługują tak naprawdę na osobne artykuły. W tym artykule w zupełności wystarczy nam wiedza, że po prostu istnieją.</p></div></div>
<p>Nawet jednak biorąc pod uwagę cztery nowe elementy nagłówka <code>Set-Cookie</code>, można spokojnie uznać, że podstawy działania ciasteczek pozostały niezmienne od <em>ponad 30 lat</em>. To wciąż są małe fragmenty informacji, które serwer może zapisać w przeglądarce, a przeglądarka odeśle je z powrotem przy każdym żądaniu HTTP.</p>
<h2 id="stare-api-czyli-wyżerając-surowe-ciasto"><a class="header-anchor" href="https://blog.comandeer.pl/pudelko-z-ciasteczkami#stare-api-czyli-wyżerając-surowe-ciasto">Stare API, czyli wyżerając surowe ciasto</a></h2>
<p>Wraz z pojawieniem się JS-a powstała potrzeba odczytywania i ustawiania ciasteczek z jego poziomu. Powstała zatem <a href="https://developer.mozilla.org/en-US/docs/Web/API/Document/cookie" rel="noreferrer noopener">własność <code>document.cookie</code></a>, Za jej pomocą można zarówno odczytywać, jak i zapisywać ciasteczka.</p>
<p>Żeby zapisać ciasteczko, ustawiamy wartość&nbsp;własności <code>document.cookie</code> na poprawną zawartość nagłówka HTTP <code>Set-Cookie</code>:</p>
<figure class="code" typeof="SoftwareSourceCode">
			<figcaption class="code__caption">
				<span class="code__title" property="programmingLanguage">JavaScript</span>
				
			</figcaption>
			<div class="code__code" translate="no" property="text">
				<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">document.cookie </span><span style="color:#D73A49;--shiki-dark:#F97583">=</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> `myCookie=wartosc;Expires=${</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> ( </span><span style="color:#D73A49;--shiki-dark:#F97583">new</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> Date</span><span style="color:#032F62;--shiki-dark:#9ECBFF">( </span><span style="color:#032F62;--shiki-dark:#9ECBFF">'2030-12-31'</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> ) ).</span><span style="color:#6F42C1;--shiki-dark:#B392F0">toUTCString</span><span style="color:#032F62;--shiki-dark:#9ECBFF">() </span><span style="color:#032F62;--shiki-dark:#9ECBFF">}`</span><span style="color:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span class="line"></span></code></pre>

			</div>
		</figure><p>Powyższy kod stworzy nowe ciasteczko o nazwie <code>myCookie</code> i wartości <code>wartosc</code>. Ciasteczko będzie ważne do 31 grudnia 2030 roku. Warto zwrócić uwagę, że wykorzystaliśmy tutaj <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toUTCString" rel="noreferrer noopener">metodę <code>Date#toUTCString()</code></a> – tworzy ona datę w formacie zgodnym ze <a href="https://datatracker.ietf.org/doc/html/rfc7231#section-7.1.1.1" rel="noreferrer noopener">standardem RFC 7231</a>. Ogólnie można uznać, że to ten sam format, który jest akceptowany w nagłówku <code>Set-Cookie</code>.</p>
<div class="note" role="note" aria-labelledby="note-8"><p class="note__label" id="note-8">Dygresja</p><div class="note__content"><p>W przypadku standardów opisywanych przez RFC, nowsze RFC nadpisują starsze RFC. Tym samym format daty został już wielokrotnie nadpisany. Po raz pierwszy pojawia się w <a href="https://datatracker.ietf.org/doc/html/rfc822#section-5.1" rel="noreferrer noopener">RFC 822</a>, potem w <a href="https://datatracker.ietf.org/doc/html/rfc1123#section-5.2.14" rel="noreferrer noopener">RFC 1123</a>, <a href="https://datatracker.ietf.org/doc/html/rfc2616#section-3.3.1" rel="noreferrer noopener">RFC 2616</a>, <a href="https://datatracker.ietf.org/doc/html/rfc2822#section-3.3" rel="noreferrer noopener">RFC 2822</a>, <a href="https://datatracker.ietf.org/doc/html/rfc5322#section-3.3" rel="noreferrer noopener">RFC 5322</a> i w końcu – <a href="https://datatracker.ietf.org/doc/html/rfc7231#section-7.1.1.1" rel="noreferrer noopener">RFC 7231</a>. Na całe szczęście, zachodziły w nim głównie kosmetyczne poprawki oraz ujednolicanie różnych standardów. Oczywiście, ujednolicanie w ramach istniejących RFC – bo ten standard wciąż jest zupełnie inny niż <a href="https://en.wikipedia.org/wiki/ISO_8601" rel="noreferrer noopener">standard ISO 8601</a>.</p></div></div>
<p>Żeby odczytać istniejące ciasteczka, można odczytać wartość własności <code>document.cookie</code>:</p>
<figure class="code" typeof="SoftwareSourceCode">
			<figcaption class="code__caption">
				<span class="code__title" property="programmingLanguage">JavaScript</span>
				
			</figcaption>
			<div class="code__code" translate="no" property="text">
				<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">console.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">log</span><span style="color:#24292E;--shiki-dark:#E1E4E8">( document.cookie );</span></span>
<span class="line"></span></code></pre>

			</div>
		</figure><p>Zwróci to ciąg tekstowy w postaci:</p>
<figure class="code" typeof="SoftwareSourceCode">
			<figcaption class="code__caption">
				<span class="code__title" property="programmingLanguage"></span>
				
			</figcaption>
			<div class="code__code" translate="no" property="text">
				<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span>cookie1=wartosc; cookie2=wartosc</span></span>
<span class="line"><span></span></span></code></pre>

			</div>
		</figure><p>Innymi słowy: jest to taki sam format, jaki ma nagłówek HTTP <code>Cookie</code>.</p>
<p>Stare JS-owe API ciasteczkowe jest, cóż, <em>proste</em>. I na tym jego lista zalet się kończy. Ustawianie zmiennej na wartość A, by następnie odczytać z niej wartość&nbsp;B samo w sobie brzmi jak słaby projekt API. Do tego dochodzą zwracanie ciasteczek w formie jednego, długiego ciągu znaków, który trzeba samemu parsować, oraz synchroniczność samego API. A mimo to przez lata nie doczekaliśmy się niczego sensowniejszego.</p>
<h2 id="nowe-api-czyli-przechowując-ciasteczka-w-słoiku"><a class="header-anchor" href="https://blog.comandeer.pl/pudelko-z-ciasteczkami#nowe-api-czyli-przechowując-ciasteczka-w-słoiku">Nowe API, czyli przechowując ciasteczka w słoiku</a></h2>
<p>Dopiero całkiem niedawno doczekaliśmy się nowego API, <a href="https://cookiestore.spec.whatwg.org/" rel="noreferrer noopener">Cookie Store</a>. Składa się ono z dwóch zasadniczych części: <a href="https://developer.mozilla.org/en-US/docs/Web/API/CookieStore" rel="noreferrer noopener"><code>CookieStore</code></a> oraz <a href="https://developer.mozilla.org/en-US/docs/Web/API/CookieStoreManager" rel="noreferrer noopener"><code>CookieStoreManager</code></a>.</p>
<div class="note" role="note" aria-labelledby="note-9"><p class="note__label" id="note-9">Dygresja</p><div class="note__content"><p>Tak, też uważam, że to <a href="https://github.com/whatwg/cookiestore/issues/48" rel="noreferrer noopener">powinno się nazywać Cookie Jar API</a>!</p></div></div>
<p>Przy ustawianiu i odczytywaniu ciasteczek potrzebny będzie nam <code>CookieStore</code>. Każda strona jest już w niego wyposażona, jako <a href="https://developer.mozilla.org/en-US/docs/Web/API/Window/cookieStore" rel="noreferrer noopener">własność <code>window.cookieStore</code></a>:</p>
<figure class="code" typeof="SoftwareSourceCode">
			<figcaption class="code__caption">
				<span class="code__title" property="programmingLanguage">JavaScript</span>
				
			</figcaption>
			<div class="code__code" translate="no" property="text">
				<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">await</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> cookieStore.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">set</span><span style="color:#24292E;--shiki-dark:#E1E4E8">( </span><span style="color:#032F62;--shiki-dark:#9ECBFF">'testowe'</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, </span><span style="color:#032F62;--shiki-dark:#9ECBFF">'jakaś wartość'</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> ); </span><span style="color:#6A737D;--shiki-dark:#6A737D">// 1</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">await</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> cookieStore.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">set</span><span style="color:#24292E;--shiki-dark:#E1E4E8">( { </span><span style="color:#6A737D;--shiki-dark:#6A737D">// 2</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">	name: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">'testowe2'</span><span style="color:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">	value: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">'ęą'</span><span style="color:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">	sameSite: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">'strict'</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">} );</span></span>
<span class="line"></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">console.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">log</span><span style="color:#24292E;--shiki-dark:#E1E4E8">( </span><span style="color:#D73A49;--shiki-dark:#F97583">await</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> cookieStore.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">getAll</span><span style="color:#24292E;--shiki-dark:#E1E4E8">() ); </span><span style="color:#6A737D;--shiki-dark:#6A737D">// 3</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">await</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> cookieStore.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">delete</span><span style="color:#24292E;--shiki-dark:#E1E4E8">( </span><span style="color:#032F62;--shiki-dark:#9ECBFF">'testowe'</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> ); </span><span style="color:#6A737D;--shiki-dark:#6A737D">// 4</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">console.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">log</span><span style="color:#24292E;--shiki-dark:#E1E4E8">( </span><span style="color:#D73A49;--shiki-dark:#F97583">await</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> cookieStore.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">get</span><span style="color:#24292E;--shiki-dark:#E1E4E8">( </span><span style="color:#032F62;--shiki-dark:#9ECBFF">'testowe'</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> ) ); </span><span style="color:#6A737D;--shiki-dark:#6A737D">// 5</span></span>
<span class="line"></span></code></pre>

			</div>
		</figure><p>Na sam początek dodajemy przy pomocy <a href="https://developer.mozilla.org/en-US/docs/Web/API/CookieStore/set" rel="noreferrer noopener">metody <code>CookieStore#set()</code></a> nowe ciasteczko o nazwie <code>testowe</code> i wartości <code>jakaś wartość</code> (1). Jeśli chcemy podać więcej opcji przy tworzeniu ciasteczka, można przekazać do metody <code>#set()</code> obiekt z opcjami (2). Akceptowana jest większość opcji z nagłówka HTTP <code>Set-Cookie</code>, oprócz <code>Secure</code> (bo Cookie Store API i tak wymaga HTTPS) i <code>HttpOnly</code> (bo ciasteczka dostępne wyłącznie przez HTTP nie mają&nbsp;sensu w JS-ie). Następnie przy pomocy <a href="https://developer.mozilla.org/en-US/docs/Web/API/CookieStore/getAll" rel="noreferrer noopener">metody <code>#getAll()</code></a> wyświetlamy wszystkie zapisane ciasteczka (3). Potem, przy pomocy <a href="https://developer.mozilla.org/en-US/docs/Web/API/CookieStore/delete" rel="noreferrer noopener">metody <code>#delete()</code></a> usuwamy ciasteczko <code>testowe</code> (4) i próbujemy je wyświetlić przy pomocy <a href="https://developer.mozilla.org/en-US/docs/Web/API/CookieStore/get" rel="noreferrer noopener">metody <code>#get()</code></a> (5). Z racji tego, że ciasteczka już nie ma, w konsoli pojawi się wartość <code>null</code>. Warto zwrócić przy okazji uwagę, że wszystkie metody <code>CookieStore</code> są asynchroniczne.</p>
<p>Na obiekcie <code>CookieStore</code> może też zajść <a href="https://developer.mozilla.org/en-US/docs/Web/API/CookieChangeEvent" rel="noreferrer noopener">zdarzenie <code>change</code></a>. Odpala się ono w momencie, gdy zajdzie jakakolwiek zmiana w ciasteczkach:</p>
<figure class="code" typeof="SoftwareSourceCode">
			<figcaption class="code__caption">
				<span class="code__title" property="programmingLanguage">JavaScript</span>
				
			</figcaption>
			<div class="code__code" translate="no" property="text">
				<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">cookieStore.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">addEventListener</span><span style="color:#24292E;--shiki-dark:#E1E4E8">( </span><span style="color:#032F62;--shiki-dark:#9ECBFF">'change'</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, ( { </span><span style="color:#E36209;--shiki-dark:#FFAB70">changed</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, </span><span style="color:#E36209;--shiki-dark:#FFAB70">deleted</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> } ) </span><span style="color:#D73A49;--shiki-dark:#F97583">=&gt;</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> { </span><span style="color:#6A737D;--shiki-dark:#6A737D">// 1</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">	console.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">group</span><span style="color:#24292E;--shiki-dark:#E1E4E8">( </span><span style="color:#032F62;--shiki-dark:#9ECBFF">'CookieChangeEvent'</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> ); </span><span style="color:#6A737D;--shiki-dark:#6A737D">// 4</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">	console.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">log</span><span style="color:#24292E;--shiki-dark:#E1E4E8">( </span><span style="color:#032F62;--shiki-dark:#9ECBFF">'Zmienione ciasteczka'</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, changed ); </span><span style="color:#6A737D;--shiki-dark:#6A737D">// 2</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">	console.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">log</span><span style="color:#24292E;--shiki-dark:#E1E4E8">( </span><span style="color:#032F62;--shiki-dark:#9ECBFF">'Usunięte ciasteczla'</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, deleted ); </span><span style="color:#6A737D;--shiki-dark:#6A737D">// 3</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">	console.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">groupEnd</span><span style="color:#24292E;--shiki-dark:#E1E4E8">( </span><span style="color:#032F62;--shiki-dark:#9ECBFF">'CookieChangeEvent'</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> ); </span><span style="color:#6A737D;--shiki-dark:#6A737D">// 5</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">} );</span></span>
<span class="line"></span></code></pre>

			</div>
		</figure><p>Nasłuchujemy na zdarzenie <code>change</code> (1). Gdy zajdzie, wyświetlamy informacje o nim: <a href="https://developer.mozilla.org/en-US/docs/Web/API/CookieChangeEvent/changed" rel="noreferrer noopener">listę zmienionych ciasteczek</a> (2) oraz <a href="https://developer.mozilla.org/en-US/docs/Web/API/CookieChangeEvent/deleted" rel="noreferrer noopener">listę usuniętych ciasteczek</a> (3). Dzięki <a href="https://developer.mozilla.org/en-US/docs/Web/API/console/group_static" rel="noreferrer noopener">grupowaniu w konsoli</a> (4, 5) informacje o konkretnym zdarzeniu są ładnie oddzielone od reszty komunikatów w konsoli:</p>
<figure class="figure"><a class="figure__link" href="/assets/images/pudelko-z-ciasteczkami/change-344w.avif"><picture><source type="image/avif" srcset="/assets/images/pudelko-z-ciasteczkami/change-344w.avif 344w" sizes="90vw"><source type="image/webp" srcset="/assets/images/pudelko-z-ciasteczkami/change-344w.webp 344w" sizes="90vw"><source type="image/png" srcset="/assets/images/pudelko-z-ciasteczkami/change-344w.png 344w" sizes="90vw"><img src="/assets/images/pudelko-z-ciasteczkami/change-344w.png" width="344" height="156" style="aspect-ratio: 344 / 156" alt="Przykład wyświetlonych w konsoli dwóch zdarzeń change: każde z nich zaczyna się od nazwy grupy, napisanej pogrubionym fontem, &quot;CookieChangeEvent&quot;,&nbsp;następnie dwie wcięte względem nazwy linie – &quot;Zmienione ciasteczka&quot;&nbsp;wraz z tablicą zmienionych ciasteczek oraz &quot;Usunięte ciasteczka&quot;&nbsp;wraz z tablicą usuniętych ciasteczek." loading="lazy" decoding="async"></picture></a><figcaption class="figure__caption"><p>Przykładowe zdarzenia <code>change</code></p></figcaption></figure>
<p>Z kolei <a href="https://developer.mozilla.org/en-US/docs/Web/API/CookieStoreManager" rel="noreferrer noopener"><code>CookieStoreManager</code></a> pozwala wykrywać zmiany w ciasteczkach z poziomu <a href="https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API" rel="noreferrer noopener">Service Workera</a>. Nie umożliwia jednak modyfikowania ciasteczek.</p>
<p>Jeśli chodzi o <code>CookieStore</code>, <a href="https://caniuse.com/mdn-api_cookiestore" rel="noreferrer noopener">wszystkie najważniejsze przeglądarki go wspierają</a>. Z kolei <a href="https://caniuse.com/mdn-api_cookiestoremanager" rel="noreferrer noopener"><code>CookieStoreManager</code> nie jest obsługiwany w Safari</a>.</p>
<h2 id="testowanie-czyli-pora-na-degustację"><a class="header-anchor" href="https://blog.comandeer.pl/pudelko-z-ciasteczkami#testowanie-czyli-pora-na-degustację">Testowanie, czyli pora na degustację</a></h2>
<p>Wypada jeszcze słowo poświęcić temu, w jaki sposób można testować ciasteczka. Tutaj na pomoc przychodzą <a href="https://developer.mozilla.org/en-US/docs/Learn_web_development/Howto/Tools_and_setup/What_are_browser_developer_tools" rel="noreferrer noopener">narzędzia deweloperskie przeglądarki (devtools)</a>. W przypadku Chrome interesuje nas <a href="https://developer.chrome.com/docs/devtools/application" rel="noreferrer noopener">zakładka <i lang="en">Application</i></a>. W niej z kolei znajduje się sekcja <i lang="en">Storage</i>, a w niej – <a href="https://developer.chrome.com/docs/devtools/application/cookies" rel="noreferrer noopener">opcja <i lang="en">Cookies</i></a>. Po wejściu w nią, naszym oczom ukaże się lista zapisanych w przeglądarce ciasteczek dla danej strony:</p>
<figure class="figure"><a class="figure__link" href="/assets/images/pudelko-z-ciasteczkami/chrome-1024w.avif"><picture><source type="image/avif" srcset="/assets/images/pudelko-z-ciasteczkami/chrome-440w.avif 440w, /assets/images/pudelko-z-ciasteczkami/chrome-880w.avif 880w, /assets/images/pudelko-z-ciasteczkami/chrome-1024w.avif 1024w" sizes="90vw"><source type="image/webp" srcset="/assets/images/pudelko-z-ciasteczkami/chrome-440w.webp 440w, /assets/images/pudelko-z-ciasteczkami/chrome-880w.webp 880w, /assets/images/pudelko-z-ciasteczkami/chrome-1024w.webp 1024w" sizes="90vw"><source type="image/png" srcset="/assets/images/pudelko-z-ciasteczkami/chrome-440w.png 440w, /assets/images/pudelko-z-ciasteczkami/chrome-880w.png 880w, /assets/images/pudelko-z-ciasteczkami/chrome-1024w.png 1024w" sizes="90vw"><img src="/assets/images/pudelko-z-ciasteczkami/chrome-1024w.png" width="1024" height="675" style="aspect-ratio: 1024 / 675" alt="Zakładka &quot;Application&quot; w Chrome: po lewej menu z różnymi opcjami, z których wybrana jest opcja &quot;Cookies&quot;&nbsp;w sekcji &quot;Storage&quot;; po prawej tabelka z istniejącymi ciasteczkami i ich opcjami; pod tabelką panel, w którym wyświetlane są szczegóły aktualnie wybranego ciasteczka." loading="lazy" decoding="async"></picture></a><figcaption class="figure__caption"><p>Widok ciasteczek w Chrome</p></figcaption></figure>
<p>Z poziomu tego widoku można dodawać, modyfikować i usuwać ciasteczka. Co więcej, wszystkie zmiany wprowadzone tutaj zostaną wyłapane przez zdarzenie <code>change</code> obiektu <code>CookieStore</code>. Dzięki temu można przetestować, czy nasza logika obsługi ciasteczek działa poprawnie.</p>
<p>Z kolei w przypadku Firefoksa analogiczny widok jest dostępny w zakładce <i lang="en">Storage</i> w devtools:</p>
<figure class="figure"><a class="figure__link" href="/assets/images/pudelko-z-ciasteczkami/firefox-1024w.avif"><picture><source type="image/avif" srcset="/assets/images/pudelko-z-ciasteczkami/firefox-440w.avif 440w, /assets/images/pudelko-z-ciasteczkami/firefox-880w.avif 880w, /assets/images/pudelko-z-ciasteczkami/firefox-1024w.avif 1024w" sizes="90vw"><source type="image/webp" srcset="/assets/images/pudelko-z-ciasteczkami/firefox-440w.webp 440w, /assets/images/pudelko-z-ciasteczkami/firefox-880w.webp 880w, /assets/images/pudelko-z-ciasteczkami/firefox-1024w.webp 1024w" sizes="90vw"><source type="image/png" srcset="/assets/images/pudelko-z-ciasteczkami/firefox-440w.png 440w, /assets/images/pudelko-z-ciasteczkami/firefox-880w.png 880w, /assets/images/pudelko-z-ciasteczkami/firefox-1024w.png 1024w" sizes="90vw"><img src="/assets/images/pudelko-z-ciasteczkami/firefox-1024w.png" width="1024" height="254" style="aspect-ratio: 1024 / 254" alt="Zakładka &quot;Storage&quot;&nbsp;w Firefoksie: po lewej menu z różnymi typami zapisanych danych, z których wybrana jest opcja &quot;Cookies&quot;; na środku tabelka z istniejącymi ciasteczkami i ich opcjami; po prawej panel, w którym wypisane są szczegóły aktualnie wybranego ciasteczka." loading="lazy" decoding="async"></picture></a><figcaption class="figure__caption"><p>Widok ciasteczek w FIrefoksie</p></figcaption></figure>
<p>Tutaj też można dodawać, usuwać i modyfikować ciasteczka.</p>
<p>Zatem nie przedłużając już&nbsp;więcej: miłej degu… znaczy testowania ciasteczek!</p>
]]></content>
		</entry>
	
</feed>
