Everything the adaptive question bank can teach and test in Web Development, from foundations through advanced practice. Work through it in order, or start practicing and let the questions find your level.
Parsing follows that order strictly, which is why a stray ? inside a path puts everything after it in the query string, and why a # placed early silently truncates the query the server would have seen. The URL constructor is the reliable reader — hand-rolled splitting gets credentials, empty ports and relative bases wrong.
Nothing after the # is transmitted, so no backend can log it, route on it, or act on it. That makes it a safe place for local view state and a useless one for anything the server must know — and it is why some auth flows deliberately deliver a token there: the value reaches the page without ever crossing the network in a request.
All three parts must match, so http://x.com and https://x.com are different origins, and so is port 3000 against 3001. Nearly every browser security check uses this unit, which is why moving a dev server to another port breaks storage, script-readable cookies and every fetch that worked a minute earlier.
The two boundaries are deliberately different widths, and code tends to assume there is only one. Cookies mostly reason in sites, while fetch, storage and postMessage reason in origins — so a.example.com and b.example.com are isolated for the second group and share a jar for the first. Conflating them is how a cookie ends up somewhere nobody intended.
The two functions differ on delimiters. encodeURI leaves /, ?, & and # alone because it expects a whole URL; encodeURIComponent escapes them because it expects one value. Use the first on a value and an & inside it becomes a parameter separator, splitting one field into two the server reads as unrelated keys.
B. HTTP from the client: methods and the message model
The blank line is structural rather than cosmetic: it is the only thing telling a parser where the headers stop and the bytes begin. That is why a header value containing a newline is a request-smuggling vulnerability rather than a formatting slip, and why every sane framework refuses to write one.
Safe means no observable state change, and it is a promise made to everything in the path — browsers, proxies, prefetchers, link scanners and crawlers all feel free to issue one unasked. Put a deletion behind a GET and it will eventually fire without anyone clicking, most often from an email scanner following the link.
Idempotent describes the end state after N identical requests, not whether anything changed: deleting twice leaves the resource just as gone. POST makes no such promise, so a retry after a timeout can create a second order. The remedy is a client-supplied key the server deduplicates on, not a faster network.
Each request must carry everything needed to interpret it, which is why one URL can return different things to two people and why "it works for me" is so common here. Anything the server appears to remember is really storage it looked up, keyed by something the request re-presented — a cookie, a token, a parameter.
The differences are about how much can be in flight on one connection. 1.1 serializes, so a slow response stalls the ones queued behind it — which is why splitting assets across hostnames used to help. 2 multiplexes on a single connection and makes that trick actively harmful; 3 moves to QUIC so one lost packet no longer stalls every stream.
C. Status codes and redirects
The first digit is the only part a generic client needs, which is why an unrecognized 299 is handled as a 200 and an unrecognized 499 as a 400. It also assigns blame: 4xx says the request was wrong, 5xx says the server was — so an endpoint answering 500 for a malformed body is filing its own bug report under the wrong name.
Permanent redirects are cached hard, often indefinitely, so a wrong one is remembered by browsers you no longer control. 301 and 302 also historically let clients turn a POST into a GET, which is exactly why 308 and 307 exist — they preserve the method and body. Use a temporary code while the destination is still in doubt.
Answering a POST with a redirect to a GET URL leaves a plain read as the last entry in history, so a refresh or a back button re-fetches a page rather than re-submitting the form. Render HTML straight from the POST instead and the browser sits on a request it will happily replay, behind the resubmission dialog nobody reads.
A 304 carries no body, so what is saved is the payload rather than the round trip — the request still goes out and still costs latency. It can only happen when the client had a validator to send, which is why a response with neither an ETag nor Last-Modified gets re-downloaded in full every time it is checked.
401 says the credentials are missing or unusable and asks for them again — it is the one that comes with a WWW-Authenticate header. 403 says the identity is understood and still refused, so repeating the login changes nothing. Returning 401 for an authorization failure sends people into a login loop on an account already signed in.
410 tells a crawler the removal was deliberate, so the URL is dropped faster than a 404 it may retry for months. The genuinely harmful one is the soft 404 — an apologetic "not found" page served with a 200 — because every automated consumer believes the status line, indexes the apology, and reports the site as healthy.
D. Headers, caching and content negotiation
The browser trusts the declared type over the file name, so an image served as text/html can execute. Where the declaration is absent or wrong, sniffing guesses from the bytes, and that guess is the attack: a user upload shaped like markup becomes a page on your own origin. nosniff turns the guessing off, so a mislabeled response fails instead.
The client sends weighted preferences and the server chooses, so one URL legitimately has several bodies. Vary is the half people forget: it names which request headers influenced the choice. Without it, every shared cache in the path is entitled to hand the next visitor whichever variant it happened to store first.
The header widens the cache key from a URL to a URL plus the named headers' values. Negotiate on Accept-Encoding or Accept-Language without it and a proxy will serve a compressed body to a client that cannot decode it, or one language to everyone behind it. Vary: * is the blunt version and effectively disables sharing.
Freshness counts from when the response was generated, not from when this client received it — the Age header accounts for time already spent in a cache along the way. Cache-Control also overrides Expires wherever both appear. A long lifetime on a file whose URL never changes is what strands people on an old build.
no-store forbids writing the response down anywhere, which is what a bank statement needs. no-cache permits storage but requires revalidation before each reuse, so the common case is a cheap 304 with no body. Reaching for the first when you meant the second throws away that saving and re-downloads everything, every time.
The server issues an opaque token, the client returns it in If-None-Match, and a match produces a 304. Strong versus weak matters once ranges are involved: a weak validator claims only that the representation is semantically the same, which is enough to skip a full re-read and not enough to resume a partial download.
E. Cookies and their attributes
Each cookie needs its own header line, so attributes after the first ; belong to that cookie alone and cannot be shared between them. Once stored it is attached automatically to every matching request — including ones your code never issued, like an image tag or a form posted from another site. That automation is what CSRF exploits.
Omitting the attribute is the narrow choice: the cookie belongs to the exact host that set it. Adding Domain=example.com widens it to every subdomain, and a subdomain receiving it cannot narrow it again. That is the mechanism by which one neglected subdomain exposes a session set for the parent.
With neither attribute the cookie dies with the browser session, which is the right default for a login and a surprise if you assumed persistence. Max-Age counts seconds forward and wins where both appear, and it sidesteps the classic failure of Expires being evaluated against a user's badly-set clock.
The flag removes the cookie from document.cookie so injected script cannot read and exfiltrate it. What it does not do is stop that script from using the session: the browser still attaches the cookie to requests the script makes, so the attacker acts as the user without ever holding the value. It raises the cost of theft, not of abuse.
It restricts transmission to HTTPS and makes no claim about who wrote the cookie or whether it was altered. An attacker on a plain-HTTP page of the same host can still set one that the secure page later reads, because cookie scope ignores the scheme. Integrity has to come from signing the value, not from this flag.
Lax still travels on a top-level GET navigation, so following a link keeps you signed in while a cross-site form POST does not. Strict withholds it even then, which makes arriving from an external link look signed out. None restores the old unrestricted behavior and is rejected outright unless Secure is also set.
Cookies predate origins and never caught up: they key on host and path only. Two dev servers on different ports of localhost therefore share one jar and overwrite each other's sessions, and a cookie written over HTTP is visible to the HTTPS page on the same host. Never reason about cookie isolation with the origin rules.
F. Same-origin policy, CORS and cross-origin access
The request usually goes out and the server usually acts on it — what the browser withholds is your script's ability to read the answer. That distinction explains why a blocked cross-origin POST can still have created a record, and why none of this protects an endpoint from anything that is not a browser.
Embedding predates fetch by years, so images, scripts, stylesheets and form submissions are all permitted across origins by design; the browser simply refuses to let script read their contents. A form therefore reaches a third-party endpoint with cookies attached, which is precisely the gap anti-forgery tokens exist to close.
A request escapes preflight only when it uses GET, HEAD or POST, adds no header outside a small allowed set, and carries one of three content types — plain text, multipart form data, or urlencoded. Sending JSON puts Content-Type: application/json outside that set, which is why nearly every API call is preflighted.
The browser first sends an OPTIONS describing the method and headers it intends to use, and proceeds only if both are permitted. That probe must be answered with a success and without authentication, so a gateway demanding a token on every route blocks the very request that would have authorized the real one.
In credentialed mode a wildcard is refused: the browser requires the exact origin echoed back, plus an explicit header allowing credentials. Echoing whatever arrived in Origin is the tempting shortcut and recreates the hole the rule exists to close, so the value has to be checked against an allowlist before it is reflected.
Each half fails silently on its own. Omit the client option and no cookie is sent, so the server sees an anonymous caller; omit the server header and the response is discarded after it arrives. Both produce the same symptom — what looks like a logged-out 401 — which is why it is worth checking the two independently.
The rules are enforced by the browser on behalf of the user's other tabs, so a command-line client, a server-side proxy or a mobile app ignores them completely. A permissive policy does not expose an otherwise-authenticated API, and a strict one protects nothing that needs no credentials. The server still has to check.
G. The document: metadata, head and SEO-relevant markup
The declaration is a mode switch rather than a statement about the document. Without it the browser emulates a 1990s box model in which padding and borders count inside a declared width, and percentage heights resolve differently — so a layout that is subtly wrong everywhere is worth checking here before the CSS is blamed.
The parser has to assume an encoding before it can read anything, so the declaration must arrive before that assumption hardens — within the first 1024 bytes. Past that the document is reparsed or the guess sticks, and the tell is mojibake in the opening paragraph. A long comment or an injected tag above it is the usual cause.
Without it a phone lays the page out at a pretend desktop width and scales the result down, which is why the text is tiny rather than reflowed. The tag tells the browser to use the device's real width instead. It is also where user-scalable=no gets set, which takes zoom away from the people who most need it.
One per document, doing three jobs at once — the tab label, the default bookmark name and the clickable line in search results — so it is read out of context far more often than in it. Front-load the words that distinguish this page; leading with the site name makes every narrow tab look identical.
It nominates which of several equivalent URLs should be indexed, collapsing tracking parameters, trailing slashes and print variants into one. It is a hint rather than an instruction, and it must be absolute and self-consistent: a page whose canonical points somewhere that canonicalises back forms a loop a crawler resolves by ignoring both.
They act at different stages, and combining them backfires. Disallowing a URL stops the crawl, so the directive inside the page is never read — and the URL can still be listed from inbound links alone, with no description. To remove a page from an index, permit the crawl and let the page say it does not want indexing.
Both are machine-readable claims embedded in markup that no visitor ever sees: JSON-LD feeds search features, Open Graph and its siblings feed the card a shared link unfurls into. Because nothing visible breaks when they are wrong, they drift silently and the error surfaces weeks later as a share preview carrying last year's title.
H. HTML semantics and content structure
Choosing a real button brings a role, focusability, Enter and Space handling and form submission for free; a div with a click handler brings none of them and has to rebuild each one correctly. Most accessibility defects are not missing ARIA — they are behavior discarded by picking the wrong element and never replaced.
Assistive technology builds an outline from the levels and lets people jump between them, so skipping from second level to fourth reads as a section that went missing. Size is a styling decision: restyle a heading that looks too large rather than demoting it, and never mark a line up as a heading merely to get the weight.
The test for article is whether the content would still make sense somewhere else, which is why both a blog post and a single comment qualify. section groups related content that deserves a heading, nav is for major navigation blocks rather than any list of links, and aside is genuinely tangential. Wrapping everything in divs costs the outline.
em and strong state something about the content and can be announced; i and b only change how it looks. The practical test is whether the meaning survives being read aloud — a ship name or a foreign phrase is the presentational pair, a warning is not. Using bold for every important phrase removes the emphasis for anyone listening.
Header cells with an explicit scope, plus a caption, are what let a screen reader announce which row and column a value belongs to; without them a data table is an unlabeled grid of numbers. A layout table is worse than useless: it imposes a reading order the visual design does not have, and it is traversed cell by cell.
details, dialog and progress arrive with keyboard handling, focus management and the right announcements already correct. A hand-built modal nearly always misses one of trapping focus, restoring it on close, or responding to Escape — so the built-in element is simultaneously the cheaper option and the more accessible one.
I. Forms and constraint validation
The default urlencoded body has no way to carry binary, so a file input submitted without the multipart encoding sends only the file's name. Nothing errors and nothing warns — the server receives a field holding a plausible string — which is why the bug is usually discovered through an empty upload directory rather than a failure.
It is name, not id, that appears in the submitted body, so a control carrying only an id is invisible to the server while looking correctly wired in devtools. Rename a field without updating the handler and the value quietly stops arriving; the form still submits and still returns a success page.
Pairing for with the control's id, or wrapping the control entirely, buys two things at once: assistive technology announces the label with the field, and clicking the text focuses or toggles it — which is what makes a small checkbox usable. A styled div of text beside an input has neither property.
The type decides which keyboard a phone offers, how the value is parsed and which validity rules apply — a number input simply refuses to hand back non-numeric text, returning an empty string instead. It also changes the format you receive: a date control yields a normalized string regardless of how it is displayed, so never parse what is on screen.
Validity is tracked from the moment the page loads, so :invalid matches an untouched empty field and lights the whole form red before anyone has typed a character. :user-invalid waits for interaction first, which is why styling against it produces the behavior people actually expect from a form.
The entire check lives in the page, and a novalidate attribute, a devtools edit or a request made without a browser bypasses it. Treat it as an interaction improvement — telling someone early, before a round trip — and revalidate everything on the server, which is the only place that decides what you are willing to store.
A shared name is what makes radios one group, and it is also what gives them arrow-key navigation; different names produce several independent buttons that can all be on at once. Unchecked boxes submit nothing at all rather than a false value, so the server cannot tell "unticked" from "field never existed".
A disabled control is skipped on submit, cannot be focused and is usually absent from the accessibility tree, so its value never reaches the server. A readonly one cannot be edited but is still focusable and still submitted. Greying out a field with disabled when you needed its value is how the value goes missing.
J. The DOM tree and manipulating it
The parser builds a tree once and everything afterwards edits the tree, so the original bytes may no longer describe anything on screen. The parser also repairs as it goes — relocating a stray element out of a table, closing a tag you forgot — so the two can differ before a single line of script has run.
Whitespace between tags is a text node, so on any formatted document childNodes is full of them while children holds only elements. firstChild is the classic trap: it is usually the newline after the opening tag. That is why firstElementChild exists, and why minifying the HTML makes the bug vanish mysteriously.
querySelectorAll snapshots into a static list, so it is safe to iterate while editing the tree; the older getters return a live collection that re-queries as you read it. The snapshot costs a full traversal up front, and that cost is exactly what buys the guarantee that the list will not move underneath you.
Removing items while looping over a live collection skips every other element, because the collection shrinks as the index advances — the familiar "half of them are still there" outcome. It also means caching the length is a behavior change rather than an optimization. Copy into an array first whenever the loop mutates.
One parses its input as markup, so any user-supplied string is an injection point; the other inserts characters literally and cannot execute anything. Note that a <script> tag inserted this way does not run while an image with an error handler does — so "there is no script tag in it" is not a safety argument.
A node has exactly one parent, so appending one that is already in the tree removes it from where it was; there is no implicit copy. That is convenient for reordering and surprising in a loop that inserts the same element repeatedly and ends up with one. cloneNode makes the copy, and it does not bring listeners with it.
The attribute holds the initial value and the property the current one, and they track each other only until the user types. After that, reading the attribute returns the original and setting it appears to do nothing at all. Checkboxes behave the same way, which is why resetting a form means calling reset() rather than rewriting attributes.
K. The event model: phases, delegation and custom events
An event travels from the root down to the target and then back up, and a listener picks which pass it hears when it registers. Capture is the only way to see an event before a descendant can cancel its travel, which is why analytics and error handlers register there while ordinary handlers use the upward pass.
target is where it started and never changes; currentTarget is whichever element's listener is running right now, and it is only meaningful during dispatch — read it inside a later timeout and it is null. Confusing them is what makes a delegated handler act on the icon inside a button rather than the button.
They address unrelated things: one stops other listeners hearing the event, the other cancels the browser's own action — following a link, submitting a form, ticking a box. Calling the first to stop a navigation does nothing at all, and calling it reflexively breaks every ancestor that was legitimately listening.
One listener on a stable ancestor, plus closest() to identify what was actually hit, survives elements being added and removed — the case where per-element listeners quietly stop existing. It also trades thousands of registrations for a single check per event, and it is the only approach that works for content that arrives later.
focus, blur and an element's own scroll fire on the target and stop there, so delegating them from a container catches nothing — with no error to explain it. focusin and focusout are the bubbling twins for the first two, and for anything else registering on the capture pass is how you hear it from above.
Removal matches on function identity plus the capture flag, so passing a fresh arrow function to both calls removes nothing and the original keeps firing. Either keep the reference, or let the platform handle it: once detaches after a single call, and an AbortSignal cancels a whole group of listeners at one stroke.
A custom event carries its payload on detail, the only property the constructor copies for you. Both bubbling and composition default to false, so a component that dispatches without setting them fires something nobody upstream can hear — and from inside a shadow root it does not escape the component at all.
L. Loading, rendering and the page lifecycle
The parser halts at the tag, fetches if it has to, executes, and only then continues — because the script might write into the document at that point. A blocking script in the head therefore delays every byte of body markup behind it, which is why the old advice was to put them last and why an attribute is the modern answer.
Both download without blocking the parse; they differ on when they run. defer waits for parsing to finish and preserves source order, which is what a script depending on another one needs. async executes the instant its download lands, in whatever order that happens — fine for independent third-party code, and intermittently broken for anything else.
The first fires once the tree is built and deferred scripts have run, while images and stylesheets may still be arriving; the second waits for all of it. Measuring anything user-facing against the later event makes a page look slow because of a footer image nobody scrolled to, and doing setup there delays it for no reason.
Style resolves, layout computes geometry, paint fills pixels, compositing assembles the layers — once per frame, in that order. Changing a property that only affects the final stage skips the expensive ones entirely, which is the whole reason animating a transform stays smooth while animating a position property does not.
Writes are batched until something needs a measurement, at which point the browser must compute geometry immediately to answer. A loop that alternates a read with a write therefore pays for a full layout on every iteration. The fix is ordering rather than fewer reads: measure everything first, then apply every change.
The callback runs immediately before the next paint, so the work lands in the same frame as the visual change it drives, and the whole loop pauses in a background tab. A timer set to roughly a frame drifts against the real refresh rate and keeps running when nothing is visible, producing jitter that gets blamed on the machine.
A page restored from the back-forward cache resumes with its JavaScript state intact, so nothing re-runs — no load event, no fresh request, and a stale counter still on screen. The pageshow event with its persisted flag is the hook for that case, and registering an unload listener disqualifies the page from the cache entirely.
M. Links, navigation and history
An anchor with an href is reachable by Tab, activates on Enter, and offers open-in-new-tab and copy-link from the context menu; a button activates on Enter and Space and navigates nowhere. Styling one to look like the other is fine — swapping their semantics is not, because the keyboard contract travels with the element.
Without the opt-out, the opened page receives a handle to the window that opened it and can navigate that window elsewhere — enough to replace your tab with a convincing login page while attention is on the new one. Browsers now imply the protection for links, but not for window.open, where it still has to be requested.
The policy decides how much of the current URL the next site is told — the whole thing, the origin only, or nothing — and modern defaults trim to the origin when leaving for another site. It matters most where the path is itself sensitive: a password-reset link or a search results URL should not travel outward in a header.
One adds an entry and the other overwrites the current one, and the real question is what the back button should do. Replace suits a state nobody should return to — a filter being typed character by character, a redirect after signing in — otherwise the history stack fills with steps the user has to walk back through one at a time.
Changing the URL from script notifies nothing, so a router waiting for the event to render will miss every navigation it performed itself. The event is for movement through history — the back and forward buttons, or a scripted go — which means application code has to call its own render path directly and keep the listener for the rest.
A document navigation resets the scroll position, moves focus to the top of the new page, updates the title and announces the change. Replacing the body with script preserves none of that, so a route change can leave keyboard focus inside the page you just left and a screen reader saying nothing at all. Each behavior has to be rebuilt deliberately.
N. Fetch and talking to a server from script
The promise settles as soon as the headers arrive, so a 500 counts as a completely successful fetch — rejection is reserved for DNS failures, aborts and blocked cross-origin reads. A try/catch with no status check therefore treats an error page as data, and the failure surfaces later, while something tries to parse it.
The body is a stream and reading it drains it, so a second attempt on the same response throws. Read once into a variable, or clone before the first read — cloning afterwards is too late. The buffering a clone implies is precisely why it is opt-in rather than the default behavior.
The default sends cookies only to the same origin, so a cross-origin call that looks identical to one the page makes arrives completely unauthenticated. Switching to include fixes the client half and immediately obliges the server to name the exact origin and permit credentials, since a wildcard is refused in that mode.
no-cors does not lift the restriction; it downgrades the request so it is permitted to be sent, and hands back an opaque response with no status, no headers and no readable body. That is useful for putting a file into a cache and useless for reading anything — so reaching for it to get around a blocked read produces a response that always looks empty.
A signal passed in lets you cancel the request later, at which point the promise rejects with an error named for the abort. Distinguish it by name or every deliberate cancellation is logged as a failure. One controller can abort several requests at once, which makes it the natural cleanup when a view is torn down.
The cache option chooses how the HTTP cache is consulted, from bypassing it entirely to insisting on it, without changing any response header. Setting redirects to manual hands back an opaque result instead of following. Both matter most inside a retry wrapper, where silently following a redirect to another host can leak a header you attached.
O. Client-side storage
Every read and write blocks the main thread, so a large value parsed during startup delays first paint directly. It also stores strings only, so objects round-trip through JSON and come back with dates as text unless you convert them. The quota is a few megabytes and exceeding it throws rather than evicting anything.
The per-tab store is cleared when the tab closes, so two tabs of the same site hold independent copies — and duplicating a tab starts it with a snapshot of the original. The persistent store is shared across every tab of the origin and survives restarts. Choosing wrongly shows up as state leaking between tabs, or stubbornly not.
The event is delivered to other tabs of the origin and deliberately not to the one that wrote, so a handler expecting to hear its own change never runs. That makes it a usable cross-tab channel — signing out everywhere at once, for instance — provided the writing tab also updates itself directly rather than waiting.
Records live in object stores keyed by a path or an auto-increment, with secondary indexes for other lookups, and every operation runs inside a transaction over named stores. The API is asynchronous and event-based, which is why raw use is so verbose, and values are structured-cloned objects — including blobs — rather than strings.
A transaction stays open only while requests keep it busy; the moment the event loop goes idle it commits itself. Awaiting a network call in the middle of one therefore leaves a closed transaction and an inactive-state error on the next request. Fetch first, then open the transaction and write — never the other way round.
The axes are cost and capability rather than taste. Cookies ride along on every matching request and must stay tiny; Web Storage is synchronous strings capped at a few megabytes; IndexedDB is asynchronous structured data with real capacity; the Cache API holds whole responses for a worker to replay. Only the first is visible to the server.
P. Service workers and PWA basics
It is an event-driven worker sitting between the page and the network, able to answer a request from a cache, rewrite it, or pass it through untouched. It has no DOM and no page lifetime — the browser starts and kills it around events — so anything held in a module variable is gone by the time the next request arrives.
Registration demands a secure context, because a worker that can rewrite responses would be an ideal tool for a network attacker; localhost is exempted so development still works. Scope defaults to the script's own directory and can never be widened above it, which is why a worker served from an assets folder cannot control the site root.
Install is where a precache is filled, and the only phase whose completion the browser will wait for. Activate is where old caches are deleted, because it is the first moment no page is still relying on them. Fetch then runs for every request in scope, including ones the page never initiated itself.
A worker registered during a page load does not control that page, because the document was already fetched with whatever controller it had. So the caching you just installed appears to do nothing until the next navigation. Claiming the clients opts into taking over early, at the cost of the same page switching strategies mid-session.
Cache-first is fast and serves yesterday's content until something explicitly updates it. Network-first is fresh and pays the full latency, including on a flaky connection where the timeout is the real cost. Returning the cached copy while refreshing in the background splits the difference — and means the user always sees the previous visit's version.
A prompt requires a linked manifest carrying a name, a start URL, a display mode and icons at the expected sizes, all served securely, plus a registered worker with a fetch handler. Missing any single one produces no error whatsoever — the prompt simply never appears, which is exactly why the devtools checklist exists.
Q. Web Components and the shadow DOM
The hyphen guarantees the name can never collide with a future standard element. Registration is global and one-shot, so defining the same name twice throws — which bites when two copies of a library end up on the page. An unknown hyphenated tag is still valid markup and renders as an inline box until something defines it.
The connected callback can run several times if the element is moved, so it has to be safe to repeat and has to undo itself on disconnect. Attribute changes are only reported for names listed in the static observed-attributes array, which is the usual explanation for a callback that appears never to fire.
A shadow root hides its tree from outside selectors and from page CSS, so the component cannot be restyled by accident — and cannot be restyled on purpose either. Styles declared inside are contained just as firmly. Closed mode goes further and withholds the root from script entirely, including from the code that created it.
Slotted content is merely rendered in the shadow tree; it still belongs to the outer document, so page CSS styles it and the page's own selectors find it. That is the opposite of what encapsulation suggests, and it is why a component can never rely on the appearance or structure of anything it was handed.
Custom properties inherit, so they cross the boundary and become the intended theming channel. A named part exposes an internal element to outside rules, and a dedicated selector styles passed-in nodes from within. Anything not deliberately exposed stays unreachable, which means a component has to publish its own styling surface on purpose.
An event raised inside a shadow root needs to be marked as composed before it can escape at all, and once outside its target is rewritten to the host so internal structure is not leaked. Code upstream therefore cannot tell which inner node was clicked — that is the encapsulation working rather than something to route around.
R. Images, media and responsive markup
The attributes give the browser an aspect ratio before a single byte of the file arrives, so it can reserve the correct box instead of collapsing to nothing and jumping when the image lands. They coexist with CSS that overrides the size as long as one dimension is auto. Omitting them is what produces the shift that makes people lose their place.
Alternative text replaces the image's function rather than describing its pixels, so a linked logo should name the destination. An explicitly empty value marks it decorative and removes it from the reading order, which is right for a border or a spacer. Omitting the attribute altogether is a third thing again — some readers fall back to announcing the file name.
With width descriptors the browser must know how wide the image will be laid out before it has parsed the CSS, and that is what the companion attribute supplies. Leave it out and everything falls back to the full viewport width, so the largest file is chosen every time. The density form needs no such hint because the layout size is fixed.
Candidate sources let you swap crops at breakpoints or offer formats the browser may not support, and the first match wins — so their order is significant. The image element inside is both the fallback for older browsers and the place the alternative text lives. Use this when the picture itself must change; descriptors alone cover merely wanting fewer pixels.
Deferring an offscreen image saves real bytes. Applying it to the image at the top of the page delays the very element that defines the largest paint, because the browser now has to establish that it is visible before fetching it. The heuristic is blunt and reliable: anything likely to be in the first screenful should load eagerly.
Playback without a user gesture is permitted only when there is no sound, so a video with audio silently refuses and the play promise rejects. That rejection is worth handling — ignoring it leaves a poster frame and no explanation of why nothing happened. Starting muted and offering an obvious unmute control is the pattern that works everywhere.
S. Observers, workers and other platform APIs
The browser reports when an element crosses a threshold relative to a root, off the main thread, so there is no scroll handler to throttle and no geometry read per frame. Callbacks are asynchronous and batched, which means each entry describes a moment slightly in the past — ideal for triggering a load, wrong for anything that must track scrolling exactly.
It reports an element's own box changing, which a window listener cannot see when the cause is content or a flex sibling rather than the viewport. Because the callback runs after layout, resizing something inside it schedules another pass, and the browser reports a loop-limit error instead of hanging — that message means the handler is feeding itself.
Changes are collected and delivered as a batch at the end of the current task, so several insertions arrive in one callback and a node added then removed can appear as two records. Nothing is watched by default: each category is opted into explicitly, and a separate flag decides whether descendants count at all.
A worker is a genuine parallel thread with its own global scope, no DOM access and no shared variables; everything crosses as structured-cloned messages, so handing over a large array copies it unless it is explicitly transferred. That copying cost is the thing to measure — a computation too small to dwarf it runs faster left where it was.
The trade is direction against complexity. Server-sent events are one-way, ride on ordinary HTTP, and reconnect automatically while remembering the last id; sockets are bidirectional after an upgrade and leave reconnection to you; repeated requests are neither but survive every proxy. Choose by whether the client genuinely needs to talk back.
The handshake is an HTTP upgrade, yet the cross-origin rules stop applying to it — no header is required to permit the connection. That means any page anywhere can open a socket to your server with the user's cookies attached, so the server has to read the origin header and refuse for itself. Nothing in the browser will do it.
Drawing commands paint pixels and are then forgotten. There is no tree, so nothing drawn can be focused, read by assistive technology, found by a page search or hit-tested without your own coordinate arithmetic. Anything meaningful rendered there needs a parallel accessible representation, which is why serious charts ship both.
T. HTTPS, CSP and the browser's security surface
These capabilities are simply absent over plain HTTP — the property is undefined rather than throwing a permission error — so a feature that works locally and disappears on a staging server is usually this. Localhost counts as secure so development is unaffected, which is precisely what hides the problem until the first deployment.
Loading an insecure subresource into a secure page gives away the protection the page existed for, so browsers upgrade what they can and block the rest — scripts and frames outright, images increasingly. The page still renders without the blocked pieces, so the symptom is a missing widget and a console warning rather than an obvious failure.
A policy names where scripts, styles, frames and connections may come from and the browser refuses everything else. It constrains what an injection can do rather than preventing one, so it is worth having and is no substitute for escaping output. Report-only mode measures a policy against real traffic before it starts breaking the site.
Permitting inline script to make your own scripts work also permits every injected one, which is most of what the policy was buying. A per-response nonce, or a hash of the exact script text, admits the ones you meant and nothing else — and a nonce reused across responses is guessable, which makes it no better than the blanket permission.
These are the points where a string stops being data: two of them parse markup, two parse code, and a scheme-based URL does both at click time. Any of them receiving a value that came from a user, a query parameter or a response is where injection turns into execution, and they are the first places to look during review.
A bare sandbox attribute strips scripts, forms, popups and same-origin identity, and each is handed back individually by name. The combination to avoid is scripts together with same-origin: together they let the framed page reach out and remove its own sandbox attribute, which quietly undoes every other restriction you set.
U. Devtools and reasoning about a live page
The panel renders the live tree, so it already includes everything script has inserted and every repair the parser made on the way in — the transferred bytes show neither. When the two disagree, that difference is the finding: an element the parser relocated, or one some code added after load.
One pane lists every rule that matched and marks the ones that lost; the other shows the single value in force after the cascade, inheritance and unit resolution are done. Chasing a property that looks correct in the rule list usually ends in the computed view, where the declaration that actually won — often a shorthand or an inherited value — is named.
Each bar splits into time spent queued, time waiting for the first byte and time downloading, and each segment blames a different layer: queueing points at connection limits or priority, waiting at the server, downloading at payload size or bandwidth. Reading only the total is how people end up optimizing the wrong one.
A warm cache, an installed worker and an existing session make the second visit nothing like the first, so disabling the cache and forcing a full reload is the baseline — and a worker has to be bypassed explicitly on top of that. Skipping it is why a page that is slow for new visitors feels fine to the person who built it.
The console keeps a reference rather than a snapshot, so an object expanded a second after it was logged shows its state now, not at the moment of the call. That is why a logged array can appear to contain items it did not have yet. Log a clone, or a serialized string, when the value at that instant is the question.
Both the console and the network panel clear themselves on navigation by default, so a redirect chain or a form post that ends up somewhere else leaves nothing behind to read. Preserving the log is the only way to see the request that caused the move, along with anything logged in the milliseconds before the page went away.
Keentune is not affiliated with or endorsed by the organizations whose documentation informs these maps.
All exam, test, and product names and trademarks are the property of their respective owners and are used here for identification and reference only. Keentune is independent study practice — not affiliated with, authorized, or endorsed by any of these organizations.