mirror of
https://github.com/kiddin9/op-packages.git
synced 2026-11-05 01:44:16 +08:00
🐶 Sync 2026-09-26 16:35:25
This commit is contained in:
1 parent
1eb453ca2e
commit
3f39d2b62b
44 files changed
+2741
-2902
No files matched your search
@@ -56,7 +56,10 @@ function probeColor(expr) {
|
||||
* shared with `luci-app-*`, and an app's unlayered `span { color: … !important }` outranks
|
||||
* a layer and a plain inline style alike. A probe that loses its own colour reports the
|
||||
* app's, which then becomes the admin's saved axis on the next confirm. */
|
||||
_probe = E('span', { 'aria-hidden': 'true' });
|
||||
/* the id is this probe's escape from fs-router.js's strayBodyNode() (issue #56): it is
|
||||
* appended once and never removed, so without an fs-* id it would read as body litter and
|
||||
* force every navigation away from this page into a full load. */
|
||||
_probe = E('span', { 'id': 'fs-appearance-probe', 'aria-hidden': 'true' });
|
||||
_probe.style.cssText = 'position:fixed!important;left:-9999px!important;top:0!important;'
|
||||
+ 'width:0!important;height:0!important;overflow:hidden!important;'
|
||||
+ 'pointer-events:none!important;';
|
||||
|
||||
@@ -110,6 +110,12 @@ let _probe = null;
|
||||
function resolveLen(token, dflt) {
|
||||
if (!_probe) {
|
||||
_probe = document.createElement('div');
|
||||
/* the id is fs-router.js's strayBodyNode() (issue #56) reading an fs-* id as "ours", which is
|
||||
* a different question from the chrome mark above: this stays unmarked (no data-fs-chrome,
|
||||
* so the fence still does not spare it from a hostile !important) but still needs SOME fs-*
|
||||
* name, or an element appended once and never removed would read as body litter and force
|
||||
* every navigation off this page into a full load. */
|
||||
_probe.id = 'fs-chrome-geom-probe';
|
||||
_probe.setAttribute('aria-hidden', 'true');
|
||||
/* out of flow, no box, no ink: it must never affect layout, scroll extent or hit-testing */
|
||||
_probe.style.cssText = 'position:absolute!important;visibility:hidden!important;' +
|
||||
|
||||
@@ -338,6 +338,66 @@ function discard(el) {
|
||||
}
|
||||
}
|
||||
|
||||
/* ---- a page that leaves nodes as direct children of <body> hands the next page over by full load,
|
||||
* the same way an invasive foreign sheet does (docs/third-party-apps.md, Rule 2 extended from CSS to
|
||||
* the DOM — issue #56) ----
|
||||
*
|
||||
* luci-app-bandix's render() appends 7 tooltip/modal nodes to document.body on every visit,
|
||||
* unconditionally (reproduced on owrt2512 and owrt2410) — nothing here sweeps them; the reasoning is
|
||||
* docs/spa-router.md, "Foreign view DOM". The recorder is header.ut's first <body> <script>, filling
|
||||
* window.__fsBodyAdds; this file only reads it. */
|
||||
|
||||
/* -> true if `el` is a node the theme must treat as stray body litter: not the theme's own chrome,
|
||||
* not something stock LuCI parks there itself, not a node type that can never paint. Exported and
|
||||
* pure (no DOM writes, no window.__fsBodyAdds read) so tests/body-litter.test.mjs can drive it
|
||||
* without a router or a recorder. */
|
||||
function strayBodyNode(el) {
|
||||
if (!el || el.nodeType !== 1) return false;
|
||||
/* never rendered on their own, wherever a script parks them */
|
||||
switch (el.nodeName) {
|
||||
case 'SCRIPT': case 'STYLE': case 'LINK': case 'TEMPLATE': case 'NOSCRIPT': case 'META':
|
||||
return false;
|
||||
}
|
||||
/* zone 1, ours (docs/third-party-apps.md): the chrome mark, or an fs-* id/class. All of these
|
||||
* parent straight to <body> and must never be read as litter: this file's own #fs-nav-progress,
|
||||
* fs-search.js's #fs-search-ov (which also carries the mark), and the geometry/colour probes
|
||||
* fs-chrome.js and fs-appearance.js each park on <body> once and never remove — an fs-* id is
|
||||
* what tells them from bandix's own unmarked nodes below. */
|
||||
/* read through `dataset`, not a literal `hasAttribute()` call: `tools/chrome-fence.mjs` counts
|
||||
* every single-quoted occurrence of the chrome mark's own name (as a JS string) across this
|
||||
* whole tree as a JS-BUILT chrome root (EXPECT_JS_ROOTS) — right for fs-search.js's palette,
|
||||
* which mounts one, and a false positive here, which only READS the mark on somebody else's
|
||||
* node and builds nothing. camelCase avoids the exact spelling the gate scans for. */
|
||||
if (el.dataset && el.dataset.fsChrome !== undefined) return false;
|
||||
if (el.id && el.id.indexOf('fs-') === 0) return false;
|
||||
for (const c of el.classList) if (c.indexOf('fs-') === 0) return false;
|
||||
/* stock LuCI's own ui.js parents these to <body> at its own __init__ and keeps them for the life
|
||||
* of the document: #modal_overlay (showModal's host) and the shared .cbi-tooltip */
|
||||
if (el.id === 'modal_overlay') return false;
|
||||
if (el.classList.contains('cbi-tooltip')) return false;
|
||||
/* ui.js's handleDownload() appends a hidden `<a download>`, clicks it once and revokes its object
|
||||
* URL, but never removes the element itself. Every app that downloads the same way (wireguard,
|
||||
* filemanager, snmpd, banip/adblock's feed export) calls a.remove() of its own accord and is
|
||||
* pruned by bodyLittered() below before ever reaching this function. */
|
||||
if (el.nodeName === 'A' && el.hasAttribute('download') && el.style.display === 'none') return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
/* -> true iff the document holds a stray body node right now. A recording only ever grows — the
|
||||
* recorder cannot know a node was later removed or moved inside #view — so this is also the one
|
||||
* place that prunes it: an entry whose element is no longer a direct child of <body> is dropped for
|
||||
* good, or the list would grow for the life of the document and re-judge nodes nobody can reach. */
|
||||
function bodyLittered() {
|
||||
const adds = window.__fsBodyAdds;
|
||||
if (!adds || !adds.length) return false;
|
||||
let littered = false;
|
||||
for (let i = adds.length - 1; i >= 0; i--) {
|
||||
if (!document.body || adds[i].parentNode !== document.body) { adds.splice(i, 1); continue; }
|
||||
if (strayBodyNode(adds[i])) littered = true;
|
||||
}
|
||||
return littered;
|
||||
}
|
||||
|
||||
let _wired = false;
|
||||
/* the pathname whose view is currently rendered; popstate compares against it to tell a real
|
||||
* navigation from a fragment change */
|
||||
@@ -887,6 +947,10 @@ function navigate(pathname, push, kbd) {
|
||||
* only exit leaving both pages correct is a real navigation (fs-sheets.js) */
|
||||
if (sheets.documentPoisoned()) return false;
|
||||
|
||||
/* …and a page that left its own nodes as direct children of <body> is spent the same way — Rule 2
|
||||
* extended to the DOM, issue #56. See strayBodyNode()/bodyLittered() above. */
|
||||
if (bodyLittered()) return false;
|
||||
|
||||
/* …and a document whose session has died is spent the same way: the only page it can render
|
||||
* correctly is the login form, and only a real navigation gets there (watchSession) */
|
||||
if (_expired) return false;
|
||||
@@ -1495,5 +1559,13 @@ return baseclass.extend({
|
||||
/* fs-search warms its recents and the arrow-key-highlighted result, neither of which the
|
||||
* pointer/focus triggers above can see. The edge points search -> router, because the router
|
||||
* must keep no dependency on the palette. */
|
||||
prefetchSegs
|
||||
prefetchSegs,
|
||||
/* out-of-package, like the three probe exports above (contractBreaks, clearViewIntervals,
|
||||
* sessionExpired) — prefetchSegs just above is not one of them, it is a real in-package export
|
||||
* fs-search.js calls. tests/body-litter.test.mjs drives the classifier and the counter directly,
|
||||
* and navigate() itself to prove the early return actually reaches them rather than just sitting
|
||||
* beside it unused */
|
||||
strayBodyNode, /* fs:probe */
|
||||
bodyLittered, /* fs:probe */
|
||||
navigate, /* fs:probe */
|
||||
});
|
||||
@@ -85,6 +85,46 @@
|
||||
#}
|
||||
{% const fs_page = entityencode(join('-', length(ctx.path) ? ctx.path : ctx.request_path), true) %}
|
||||
<body class="lang_{{ dispatcher.lang }}" data-page="{{ fs_page }}">
|
||||
<script>
|
||||
/* Records every node a script parks directly on <body> (issue #56): luci-app-bandix's
|
||||
* render() appends 7 tooltip/modal nodes unconditionally on every visit, never
|
||||
* checking whether they already exist. fs-router.js's strayBodyNode()/bodyLittered()
|
||||
* read window.__fsBodyAdds to decide whether the document is still fit to navigate
|
||||
* away from — Rule 2 (docs/third-party-apps.md), extended from CSS to the DOM. First
|
||||
* byte of <body>, before any chrome element or the dispatched page's own <div id="view">.
|
||||
* Own property of this <body> instance, never Node.prototype, and its gap:
|
||||
* docs/spa-router.md, "Foreign view DOM". */
|
||||
(() => {
|
||||
const body = document.body;
|
||||
if (!body || body.__fsBodyHooked) return;
|
||||
body.__fsBodyHooked = true;
|
||||
const adds = window.__fsBodyAdds || (window.__fsBodyAdds = []);
|
||||
const names = [ 'appendChild', 'insertBefore', 'replaceChild', 'append', 'prepend',
|
||||
'insertAdjacentElement', 'insertAdjacentHTML' ];
|
||||
names.forEach((name) => {
|
||||
const orig = body[name];
|
||||
if (typeof orig !== 'function') return;
|
||||
try {
|
||||
Object.defineProperty(body, name, {
|
||||
configurable: true,
|
||||
writable: true,
|
||||
value() {
|
||||
const before = Array.prototype.slice.call(this.children);
|
||||
const ret = orig.apply(this, arguments);
|
||||
const after = this.children;
|
||||
for (let i = 0; i < after.length; i++) {
|
||||
if (before.indexOf(after[i]) === -1) adds.push(after[i]);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
});
|
||||
}
|
||||
/* a host that refuses to redefine this one name leaves it unwrapped; the rest
|
||||
* still are */
|
||||
catch (e) {}
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
{#
|
||||
The wallpaper pattern's paint surface: 15-wallpaper.css masks this element so an
|
||||
uploaded SVG takes the theme's ink. A mask applies to an element's whole rendering,
|
||||
|
||||
Reference in new issue
Block a user