The previous wording claimed affected browsers are detected by checking
whether xhr.upload is usable "not by sniffing the user agent", but Edge
and the PlayStation 4 browser are still matched by user agent regex.
Both mechanisms exist and for different reasons: feature detection only
sees a missing xhr.upload object, while those two expose the object and
simply never fire its events.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01S1bUKsok59shM69PwLMMRM
The upload test reported low and unstable numbers on Safari (macOS and
iOS) while the download test was correct.
Cause: the "start" handler in speedtest_worker.js matched every Safari
user agent and set settings.forceIE11Workaround = true. In ulTest() that
flag selects a fundamentally different, less accurate measurement path:
instead of sending one xhr_ul_blob_megabytes blob (20 MB by default) and
accumulating event.loaded from upload.onprogress, it sends a stream of
256 KB requests and adds a fixed 256 KB to totLoaded on each onload.
Progress is therefore quantised to whole requests and each request pays
a full round trip before the next one starts, so on fast or high-latency
links the measured speed is both too low and noisy.
The workaround is not needed. Its comment claims it is only required for
MPOT, but the block never checked settings.mpot. The same block also
excludes crios and fxios, which on iOS run the very same WebKit
networking stack as Safari, so the user agent test cannot have been
guarding an engine-level bug in the first place. Current WebKit
implements xhr.upload.onprogress, and the existing feature detection in
ulTest() (the try/catch that probes xhr.upload.onprogress) already
selects the workaround for browsers that genuinely lack it, without
sniffing.
Changes:
- Remove the Safari user agent block. Safari now takes the regular
upload path, the same one Chrome and Firefox on iOS already took.
- Guard the Edge and PlayStation 4 blocks with
typeof s.forceIE11Workaround === "undefined", matching how the other
quirks avoid clobbering explicitly passed settings. Previously an
explicit forceIE11Workaround: false in the start command was silently
overwritten, so the workaround was not switchable from outside the
worker. Default behaviour for those browsers is unchanged.
- Add tests/e2e/worker-browser-quirks.spec.js, which loads the worker
under spoofed user agents and asserts the resolved settings.
- Update doc.md so Safari is no longer listed as affected.
The Edge and PlayStation 4 blocks are deliberately left outside the
enable_quirks condition: those browsers have no working
upload.onprogress, so running them with enable_quirks: false would
report 0 or Fail rather than a less precise number.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01S1bUKsok59shM69PwLMMRM
The shared result image had one look, and it no longer matched the modern
frontend.
The renderer can now draw either design. The image follows the same
useNewDesign setting as the frontend, so a deployment has one switch rather
than two that can disagree, and classic remains the fallback.
- An explicit style query parameter overrides it, which is how the modern
frontend asks for the design it matches.
- The modern design reuses the frontend palette and shows the client, address
family and timestamp already present in the result data.
Fixed the container port in the `Example Frontend mode` section.
The inner container port should be `8080` instead of `80` to correctly map the frontend service.
fix: preserve the modern frontend layout in deployments (#834)
Serve the modern UI assets from frontend/ in both repository and Docker
deployments, matching the paths used by index-modern.html.
Move settings.json and server-list.json to the web root, where the modern
page resolves its configuration, and keep Docker, installation guidance, and
design-switch documentation consistent with that layout.
Add E2E coverage for:
- serving the repository unchanged with Python's static HTTP server;
- required modern UI assets and a real 404 for missing paths;
- Alpine standalone settings and modern frontend rendering.
Fixes#804
index-modern.html refers to styling/, javascript/ and images/ as if they sat
next to it, but they live under frontend/. Only docker/entrypoint.sh reconciles
that, by flattening those directories into the web root, so serving the
repository as it stands renders the page without stylesheet, script or logo.
Point the HTML at frontend/, have the entrypoint copy that directory as-is, and
move settings.json and server-list.json to the root that the page fetches them
from, so the repository layout and the deployed layout are the same. Update the
install instructions, which described the flattening.
formatSpeedtestDataForImage() assigned the timestamp to itself, so the value
reached the image exactly as the database returned it. Every other field on
that list is passed through format(); this one reads like a placeholder that
was never filled in, and static analysis flags it as a self-assignment.
How much precision the column carries is decided by the backend, and the three
schemas shipped here disagree: MySQL's `timestamp` keeps none, PostgreSQL's
`timestamp without time zone DEFAULT now()` keeps microseconds and MSSQL's
`datetime` keeps milliseconds. A PostgreSQL deployment therefore drew
"2026-08-10 02:04:13.957789" where a MySQL one drew "2026-08-10 02:04:13".
- xhr.onload now owns the complete lifecycle: checks x.status on
the captured local variable, restarts only on 2xx.
- xhr.upload.onload no longer calls testStream() to avoid race
with xhr.onload overwriting xhr[i] before status is read.
- xhr.upload.onerror also uses the captured x for consistency.
- speedtest_worker.js: moved HTTP status check from xhr.upload.onload
(body sent, response may not be available) to xhr.onload
(full HTTP transaction complete, xhr.status is reliable).
- doc.md: added full IIS web.config example with maxAllowedContentLength.
- speedtest_worker.js: xhr.upload.onload now checks xhr.status
before restarting the upload stream. Non-2xx responses (e.g.
HTTP 413 from undersized client_max_body_size) are treated as
failures instead of inflating upload speed measurements.
- doc.md: added explicit nginx/Apache/IIS configuration examples
for client_max_body_size / LimitRequestBody to prevent HTTP 413
errors on the 20MB upload test blobs.
Drop the Debian COPY comment (it only restated the code) and the
version-fragility framing in the Alpine comment.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>