The course
Beyond fetch()
Every page so far taught the same shape: the request usually
succeeds, the server processes it, and only the
response is silently withheld from your JavaScript. A blocked
fetch() rejects with a vague TypeError; a
no-cors read hands you an opaque husk. The browser hides
what happened.
This page is the counter-case. CORS also governs
images drawn to a canvas, fonts, cross-origin scripts, and — with
a different mechanism entirely — WebSocket handshakes. And here,
for once, nothing is hidden. A tainted canvas throws a
SecurityError synchronously in your own code. A blocked
font load rejects a promise you’re awaiting. A cross-origin
script’s error arrives at your window.onerror handler
(just stripped of detail). A WebSocket tells you the handshake result
outright through onopen/onerror/onclose. Four mechanisms, four observable outcomes — run each
one below. One more pair closes the page out: sendBeacon
and HTML form posts don’t observably fail at all — they
bypass CORS’s read-gate by never asking your JavaScript to read
anything back in the first place.
1 · Canvas tainting
Draw a cross-origin image to a <canvas> and try to
read the pixels back. The image paints either way — but reading it
back is CORS-gated.
Draw the cross-origin image to a <canvas>, then read a pixel back with getImageData(). Try both knobs and watch what your own JavaScript sees.
2 · Cross-origin fonts
Load a cross-origin font with the CSS Font Loading API. Fonts are always fetched in CORS mode, so the outcome is a promise that either resolves or rejects — right there in your code.
Load a cross-origin font with the CSS Font Loading API and watch whether the promise resolves or rejects.
3 · Script error suppression
Inject a cross-origin <script> that throws. Your
window.onerror handler always fires — but how much
detail it is allowed to see depends on whether the script was
CORS-loaded.
Inject a cross-origin <script> that throws, and see how much of the error your window.onerror handler is allowed to see.
4 · The WebSocket Origin check
WebSockets are the outlier: CORS does not govern them at all. The
browser sends an Origin header on the handshake but enforces
nothing on the response — so the server has to check the
origin, or any page on the web can open a socket. Connect from this page
(an allowlisted origin) and from a cross-origin frame (an unallowlisted
one) and compare.
Connect from this page
This page's origin is SITE — the one origin the server allowlists.
Not connected yet.
Connect from other.corslabs.com
A cross-origin frame plays the "outside attacker" — a different origin the server does not allowlist.
Not connected yet.
5 · sendBeacon and form posts
Every demo on this site so far has been about a response your JavaScript tried to read — and CORS is entirely a read-gate: it decides whether a script gets to see a cross-origin response, never whether the request is allowed to leave or the server is allowed to process it. Two everyday mechanisms sidestep that gate completely, simply by never asking your code to read anything back.
navigator.sendBeacon() is built for exactly this: a
fire-and-forget POST, typically used for analytics or
logging on page unload, that hands your code no response object at
all — not even the opaque husk a no-cors fetch() returns. There is nothing to gate, so CORS
doesn’t come up. The Origin header still goes out,
the server still receives and can still reject the request outright,
but your JavaScript was never going to see the outcome either way.
A plain HTML <form method="post" action="https://other-origin.example">
works the same way, for a different reason: submitting a form is a
full-page navigation, not a script-initiated
fetch(). There is no Response object for your
JavaScript to read, so there is nothing for CORS to gate — a form
can POST cross-origin, with the visitor’s cookies
attached, freely. This is precisely the CSRF connection:
Cross-Site Request Forgery relies on the browser happily sending a
credentialed cross-origin form post that CORS was never designed to
stop, because CORS only ever gated the read, and a form post never
asks to read anything. Defending against it is a separate job —
CSRF tokens, SameSite cookies (page 5), and checking
request origin/referrer server-side — not a CORS setting.
Neither of these gets a live demo on this page. Both outcomes are the absence of anything to observe — a beacon that fires with no readable result, a form that navigates away — so a button here would either do nothing visible or just navigate off this page. The teaching point is the mechanism, not a click-to-run outcome.