🤞 Sync 2026-08-12 09:27:55

This commit is contained in:
github-actions[bot]
2026-08-12 09:27:55 +08:00
parent 030d9d82a4
commit d5f9df4a19
5 changed files with 73 additions and 24 deletions
@@ -412,9 +412,13 @@ function navigate(pathname, push, kbd) {
* decided by how the user got there.
*
* So decline, exactly as the poisoned-document bail above does: speed is traded for correctness,
* never the other way. It costs ONE full load per such page — after it the <link> is in the
* document, this test passes, and every later visit is a swap again (fs-sheets.js owns the sheet
* from then on and re-lights it per page).
* never the other way. The cost is one full load per ENTRY INTO A DOCUMENT that lacks the sheet,
* not one per page ever: head.ut emits the link for the DISPATCHED node only, so each full load
* starts a document carrying exactly one such sheet and discards what the previous one had
* gathered. Within that document the page is a swap from then on (fs-sheets.js owns the sheet and
* re-lights it per page) — but two `css`-bearing pages alternating are a full load every time, in
* both directions. That is the trade taken knowingly: an unstyled page is worse than a reload, and
* no in-tree node sets `css` today.
*
* Injecting the <link> here instead would work and is deliberately not done: it would put the theme
* in charge of fetching and ordering a foreign stylesheet, which is the job fs-sheets.js exists to
@@ -325,14 +325,29 @@ function outlivesPage(el) {
* this module's because the answer is: a link inside #view is about to be deleted with the rest of
* the view, and counting it would hand the router a sheet the next dom.content() throws away —
* `luci-app-nlbwmon` returns E('link', …, L.resource('view/nlbw.css')) from render(), so that shape
* is real. Compared by PATH and by suffix: head.ut prints `{{ resource }}/{{ dispatched.css }}?v=…`,
* so neither the resource base nor the cache key is anyone's to assume. */
* is real.
*
* WHOLE PATH, NOT A SUFFIX. head.ut prints `{{ resource }}/{{ dispatched.css }}?v=…`, and the base in
* that line is the SAME value the runtime holds: header.ut hands `resource` to `new LuCI({…})`, and
* `L.resource()` joins it back exactly, so the server's href is reconstructable rather than guessable
* — only the cache key has to come off. A suffix match is what a guess costs: anchored at nothing but
* a `/`, `custom.css` matches any sheet ending in that filename, and two in-tree apps append exactly
* that to <head> at module eval — `luci-app-adblock` and `luci-app-banip` both add
* `L.resource('view/<app>/custom.css')`, outside #view, so outlivesPage() keeps them and this module
* disables rather than removes them: they stay for the life of the document. A third-party node
* declaring `"css": "custom.css"` would then read as already-carried the moment the user had passed
* through Adblock → Feeds, and the router would swap into a page whose stylesheet was never linked —
* the one outcome the guard exists to prevent.
*
* Equality also keeps the failure safe: L.path() drops a part that leaves its charset, so a malformed
* `css` yields the bare base and matches no href at all — a full load, which is the correct answer for
* a value nobody can serve. */
function documentCarries(path) {
const want = '/' + String(path).replace(/^\/+/, '');
const want = L.resource(String(path));
for (const link of document.querySelectorAll('link[rel~="stylesheet"][href]')) {
if (!outlivesPage(link))
continue;
if ((link.getAttribute('href') || '').split('?')[0].endsWith(want))
if ((link.getAttribute('href') || '').split('?')[0] === want)
return true;
}
return false;