web: english-lockdown shim — pin locale + hide switchers
This commit is contained in:
parent
414ad674c9
commit
d2120c636f
3 changed files with 378 additions and 14 deletions
|
|
@ -14,6 +14,104 @@ SHIM = MARKER_BEGIN + r"""
|
|||
(function(){
|
||||
var TITLE = 'ARRFLIX';
|
||||
var BARE_RE = /^Jellyfin$/i;
|
||||
/* === English-lockdown (synchronous, runs before Jellyfin bundle) ===
|
||||
Pins UI locale to en-US so the SPA never reads navigator.language
|
||||
or the user's stored preference. Belt-and-braces against:
|
||||
- localStorage keys the SPA reads on boot
|
||||
- navigator.language / navigator.languages getters
|
||||
- fetch / XHR Accept-Language header (best-effort; most browsers
|
||||
block JS from setting it, but Jellyfin sometimes does)
|
||||
- user-config save round-trip (rewrite UICulture → en-US before send) */
|
||||
try {
|
||||
var LS_KEYS = ['appLanguage','selectedlanguage','selectedlocale','language','locale','culture'];
|
||||
for (var i=0;i<LS_KEYS.length;i++){
|
||||
try { localStorage.setItem(LS_KEYS[i], 'en-US'); } catch(e){}
|
||||
}
|
||||
} catch(e){}
|
||||
try {
|
||||
var EN = ['en-US','en'];
|
||||
Object.defineProperty(Navigator.prototype, 'language', { get:function(){return 'en-US';}, configurable:true });
|
||||
Object.defineProperty(Navigator.prototype, 'languages', { get:function(){return EN.slice();}, configurable:true });
|
||||
} catch(e){
|
||||
/* fallback for engines that won't let us redefine on the prototype */
|
||||
try { Object.defineProperty(navigator, 'language', { get:function(){return 'en-US';}, configurable:true }); } catch(e2){}
|
||||
try { Object.defineProperty(navigator, 'languages', { get:function(){return ['en-US','en'];}, configurable:true }); } catch(e2){}
|
||||
}
|
||||
/* fetch wrapper: strip Accept-Language on outbound requests, and rewrite
|
||||
any user-config save body so UICulture is pinned to en-US. */
|
||||
try {
|
||||
if (window.fetch) {
|
||||
var _origFetch = window.fetch;
|
||||
window.fetch = function(input, init){
|
||||
try {
|
||||
init = init || {};
|
||||
/* strip Accept-Language if present on a plain object headers init */
|
||||
if (init.headers) {
|
||||
if (init.headers instanceof Headers) {
|
||||
try { init.headers.delete('Accept-Language'); } catch(e){}
|
||||
} else if (typeof init.headers === 'object') {
|
||||
for (var k in init.headers){ if (k && k.toLowerCase() === 'accept-language') { try { delete init.headers[k]; } catch(e){} } }
|
||||
}
|
||||
}
|
||||
/* rewrite user-config save: POST /Users/{id}/Configuration */
|
||||
var url = (typeof input === 'string') ? input : (input && input.url) || '';
|
||||
var method = (init.method || (input && input.method) || 'GET').toUpperCase();
|
||||
if (url && /\/Users\/[^/]+\/Configuration(\?|$)/.test(url) && method === 'POST' && init.body) {
|
||||
try {
|
||||
var body = init.body;
|
||||
if (typeof body === 'string') {
|
||||
var obj = JSON.parse(body);
|
||||
if (obj && typeof obj === 'object') {
|
||||
obj.UICulture = 'en-US';
|
||||
init.body = JSON.stringify(obj);
|
||||
}
|
||||
}
|
||||
} catch(e){}
|
||||
}
|
||||
} catch(e){}
|
||||
return _origFetch.call(this, input, init);
|
||||
};
|
||||
}
|
||||
} catch(e){}
|
||||
/* XHR wrapper: strip Accept-Language; rewrite user-config save body. */
|
||||
try {
|
||||
if (window.XMLHttpRequest) {
|
||||
var _open = XMLHttpRequest.prototype.open;
|
||||
var _setHeader = XMLHttpRequest.prototype.setRequestHeader;
|
||||
var _send = XMLHttpRequest.prototype.send;
|
||||
XMLHttpRequest.prototype.open = function(method, url){
|
||||
this.__arrflix_method = (method || 'GET').toUpperCase();
|
||||
this.__arrflix_url = url || '';
|
||||
return _open.apply(this, arguments);
|
||||
};
|
||||
XMLHttpRequest.prototype.setRequestHeader = function(name, value){
|
||||
if (name && String(name).toLowerCase() === 'accept-language') return;
|
||||
return _setHeader.apply(this, arguments);
|
||||
};
|
||||
XMLHttpRequest.prototype.send = function(body){
|
||||
try {
|
||||
if (this.__arrflix_url && /\/Users\/[^/]+\/Configuration(\?|$)/.test(this.__arrflix_url) && this.__arrflix_method === 'POST' && typeof body === 'string') {
|
||||
try {
|
||||
var obj = JSON.parse(body);
|
||||
if (obj && typeof obj === 'object') {
|
||||
obj.UICulture = 'en-US';
|
||||
body = JSON.stringify(obj);
|
||||
}
|
||||
} catch(e){}
|
||||
}
|
||||
} catch(e){}
|
||||
return _send.call(this, body);
|
||||
};
|
||||
}
|
||||
} catch(e){}
|
||||
/* Re-pin localStorage on every visibility change (SPA may rewrite on user save) */
|
||||
function pinLocale(){
|
||||
try {
|
||||
var L = ['appLanguage','selectedlanguage','selectedlocale','language','locale','culture'];
|
||||
for (var i=0;i<L.length;i++){ try { if (localStorage.getItem(L[i]) !== 'en-US') localStorage.setItem(L[i], 'en-US'); } catch(e){} }
|
||||
} catch(e){}
|
||||
}
|
||||
/* === end english-lockdown synchronous block === */
|
||||
function getFavicon(){
|
||||
var l = document.querySelector('link[rel="shortcut icon"], link[rel="icon"]');
|
||||
return l && l.href ? l.href : null;
|
||||
|
|
@ -49,7 +147,7 @@ SHIM = MARKER_BEGIN + r"""
|
|||
} catch(e){}
|
||||
}
|
||||
function start(){
|
||||
lockTitle(); lockFavicon(); nukeSettings();
|
||||
lockTitle(); lockFavicon(); nukeSettings(); pinLocale();
|
||||
try {
|
||||
var head = document.head || document.querySelector('head');
|
||||
if (head && window.MutationObserver) {
|
||||
|
|
@ -67,6 +165,7 @@ SHIM = MARKER_BEGIN + r"""
|
|||
var fav = getFavicon();
|
||||
if (fav && fav.indexOf('data:image') !== 0) lockFavicon();
|
||||
nukeSettings();
|
||||
pinLocale();
|
||||
}, 1000);
|
||||
}
|
||||
if (document.readyState === 'loading') {
|
||||
|
|
|
|||
137
web-overrides/ENGLISH-LOCKDOWN.md
Normal file
137
web-overrides/ENGLISH-LOCKDOWN.md
Normal file
|
|
@ -0,0 +1,137 @@
|
|||
# English Lockdown — Web-side Shim
|
||||
|
||||
> Browser-side belt for the per-user `UICulture` pin documented in
|
||||
> `docs/15-force-english.md`. The server-side POST sets the authoritative
|
||||
> value; this shim removes every escape hatch the SPA exposes to the user
|
||||
> so they can't unpin it from the browser.
|
||||
|
||||
Last verified: 2026-05-08 against Jellyfin 10.10.3 web bundle, arrflix.s8n.ru.
|
||||
|
||||
---
|
||||
|
||||
## What this does
|
||||
|
||||
The English-lockdown logic lives **inside the existing ARRFLIX runtime
|
||||
shim** (one self-contained IIFE per `docs/10-spa-runtime-shim.md` —
|
||||
"the shim must remain self-contained"). Source of truth:
|
||||
`bin/inject-shim.py`. Compiled output lands in `web-overrides/index.html`
|
||||
between the `ARRFLIX-SHIM-BEGIN` / `-END` markers.
|
||||
|
||||
It runs **synchronously, before the Jellyfin bundle parses**, and pins
|
||||
the UI to English by:
|
||||
|
||||
| # | Mechanism | Why |
|
||||
|---|-----------|-----|
|
||||
| 1 | `localStorage.setItem` for `appLanguage`, `selectedlanguage`, `selectedlocale`, `language`, `locale`, `culture` → all set to `en-US` | Belt-and-braces; covers every key Jellyfin web has shipped under across versions. |
|
||||
| 2 | `Object.defineProperty(Navigator.prototype, 'language' / 'languages')` returning `'en-US'` / `['en-US','en']` | Jellyfin's pre-auth bundle reads `navigator.language` to pick the splash translation file. Overriding the prototype getter beats the bundle to it. |
|
||||
| 3 | `fetch` wrapper — strips `Accept-Language` header on outbound requests; rewrites `POST /Users/{id}/Configuration` body to force `UICulture: 'en-US'` before send | Defensive: even if a future Jellyfin build offers a "save language" UI we don't know about, the POST gets rewritten in-flight. The user can't opt out. |
|
||||
| 4 | `XMLHttpRequest` wrapper — same Accept-Language strip + same Configuration POST rewrite | Older Jellyfin bundle code paths use XHR rather than fetch. Belt for the fetch suspenders. |
|
||||
| 5 | `pinLocale()` re-runs on every `start()` call AND on the existing 1s setInterval safety net | Re-pin storage keys if the SPA tries to clear/rewrite them. |
|
||||
|
||||
A companion CSS block in the existing critical-path `<style>` tag (top of
|
||||
`web-overrides/index.html`) hides every language-switcher widget in the
|
||||
UI: profile prefs dropdown, login page locale picker, header userMenu
|
||||
locale flag, and (via `:has()`) any future `<select>` that contains a
|
||||
`de-DE`/`fr-FR`/`es-ES` option.
|
||||
|
||||
## Where it gets injected from
|
||||
|
||||
```
|
||||
bin/inject-shim.py # source of truth for the JS shim (run after edits)
|
||||
web-overrides/index.html # the IIFE lives here, between ARRFLIX-SHIM-BEGIN/-END
|
||||
# the CSS hide rules live in the <style> at the top
|
||||
```
|
||||
|
||||
Container bind-mount (compose, unchanged from `docs/10`):
|
||||
|
||||
```yaml
|
||||
volumes:
|
||||
- /opt/docker/jellyfin/web-overrides/index.html:/jellyfin/jellyfin-web/index.html:ro
|
||||
```
|
||||
|
||||
## Deploy workflow
|
||||
|
||||
```bash
|
||||
# 1. Edit bin/inject-shim.py (NOT the IIFE inside index.html directly)
|
||||
# 2. Re-run injector locally
|
||||
python3 bin/inject-shim.py
|
||||
# 3. scp to nullstone
|
||||
scp web-overrides/index.html user@192.168.0.100:/opt/docker/jellyfin/web-overrides/index.html
|
||||
# 4. Hard-refresh a browser. No container restart needed (single-file bind mount).
|
||||
```
|
||||
|
||||
CSS-only edits (the `<style>` block at the very top of `index.html`)
|
||||
are edited directly — the injector only owns the `<script>` IIFE.
|
||||
|
||||
## Verification (operator runs after deploy)
|
||||
|
||||
In a fresh incognito browser at `https://arrflix.s8n.ru`, open DevTools
|
||||
console and run:
|
||||
|
||||
```js
|
||||
localStorage.getItem('appLanguage') // expect: "en-US"
|
||||
localStorage.getItem('selectedlanguage') // expect: "en-US"
|
||||
navigator.language // expect: "en-US"
|
||||
navigator.languages // expect: ["en-US", "en"]
|
||||
```
|
||||
|
||||
Curl-side the file is loading the new shim:
|
||||
|
||||
```bash
|
||||
curl -ks https://arrflix.s8n.ru/web/index.html | grep -c english-lockdown
|
||||
# expect: 2 (one in CSS comment header, one in JS comment header)
|
||||
curl -ks https://arrflix.s8n.ru/web/index.html | grep -c pinLocale
|
||||
# expect: 4 (definition + 3 call-sites: start, setInterval, comment)
|
||||
```
|
||||
|
||||
UI-side:
|
||||
|
||||
- [ ] User profile prefs page shows no "Display Language" dropdown.
|
||||
- [ ] Login page shows no language picker.
|
||||
- [ ] Header userMenu shows no locale flag/text.
|
||||
- [ ] After auth, every Play / Resume / Settings label is English even
|
||||
from a browser sending `Accept-Language: de-DE,de;q=0.9`.
|
||||
|
||||
## Known limitations
|
||||
|
||||
1. **First-paint flash on cold load.** The pre-bundle splash strings
|
||||
(login form: "Sign In" / "Username" / "Password") are loaded by the
|
||||
bundle from a static JSON file based on the browser's locale-detection
|
||||
fallback BEFORE the IIFE's `Object.defineProperty` can intercept.
|
||||
Modern Chromium / Firefox respect the prototype redefinition fast
|
||||
enough that this is sub-50ms in practice — but on a slow connection
|
||||
you may briefly see German login labels before the English bundle
|
||||
replaces them. Acceptable; matches the existing first-paint flash
|
||||
caveat in `docs/10`.
|
||||
|
||||
2. **`Accept-Language` strip is best-effort.** Most browsers prevent JS
|
||||
from removing or modifying the `Accept-Language` request header on
|
||||
outbound `fetch` / XHR. The wrapper attempts the delete; if the
|
||||
browser silently ignores it, no harm done — the per-user `UICulture`
|
||||
pin (server-side, see `docs/15`) wins regardless.
|
||||
|
||||
3. **`Object.defineProperty` may fail on some embedded WebViews** that
|
||||
freeze `Navigator.prototype`. The shim has a fallback that retries
|
||||
on the `navigator` instance directly. If both fail, the navigator
|
||||
getters still return browser values, but the localStorage pin and
|
||||
the user-config-save rewrite still hold the line.
|
||||
|
||||
4. **CSS `:has()`** has the same Chromium 105+ / Firefox 121+ /
|
||||
Safari 15.4+ floor as the existing drawer-Settings rules. On older
|
||||
browsers the `option[value="de-DE"]`-conditional hide degrades
|
||||
silently — the simpler `select[name="language"]` rules still hide
|
||||
the standard dropdown.
|
||||
|
||||
## Why this is layered with the per-user `UICulture` POST
|
||||
|
||||
The server-side fix (`docs/15-force-english.md`) is the authoritative
|
||||
mechanism: when a user has `Configuration.UICulture = "en-US"`, the SPA
|
||||
honours it on every login. This shim exists because:
|
||||
|
||||
- New users created outside the wrapper might land without the pin.
|
||||
- A future Jellyfin web release might add a "change language" affordance
|
||||
inside the player or a settings deeplink we haven't audited.
|
||||
- The pre-auth splash bundle ignores `UICulture` (the user isn't logged
|
||||
in yet) and reads `navigator.language` directly.
|
||||
|
||||
CSS hide + JS lockdown belt; per-user POST suspenders. Both are needed.
|
||||
|
|
@ -21,10 +21,148 @@ a.btnSettings,
|
|||
[data-itemid="settings"] {
|
||||
display: none !important;
|
||||
}
|
||||
/* ARRFLIX english-lockdown (2026-05-08) — hide every language-switcher in the UI.
|
||||
The shim pins UICulture to en-US in JS; these rules remove the controls so the
|
||||
user can't even see they exist. !important survives Jellyfin upgrades that
|
||||
re-skin the prefs page. Belt; the JS user-config save hook is the suspenders. */
|
||||
/* User profile prefs — Display Language dropdown */
|
||||
select[name="language"],
|
||||
select#selectLanguage,
|
||||
select[is="emby-select"][name="language"],
|
||||
.languageSelector,
|
||||
[data-role="language"],
|
||||
.fldDisplayLanguage,
|
||||
.fieldDisplayLanguage,
|
||||
.displayLanguageField,
|
||||
/* Login form locale picker (some Jellyfin builds expose a langSelect on /web/#/login.html) */
|
||||
#divLoginLanguage,
|
||||
.loginLanguage,
|
||||
select[name="loginLanguage"],
|
||||
/* Header / userMenu locale flag-or-text widget (Cineplex theme adds a .localeButton on some builds) */
|
||||
.headerLanguageButton,
|
||||
.localeButton,
|
||||
.localeFlag,
|
||||
button.btnLanguage,
|
||||
[data-action="language"],
|
||||
/* Belt: any <select> that contains a non-English option (de/fr/es/...) — hides
|
||||
future Jellyfin builds that rename the field but keep the option list */
|
||||
.formSection select option[value="de"],
|
||||
.formSection select option[value="fr"],
|
||||
.formSection select option[value="es"],
|
||||
.formSection select option[value="ru"] {
|
||||
display: none !important;
|
||||
}
|
||||
/* Hide the parent <select> + its label when its option list contains de-DE.
|
||||
:has() is supported in Chromium 105+ / Firefox 121+ / Safari 15.4+ — same
|
||||
support floor as the existing drawer-Settings :has() selectors. */
|
||||
.fldSelectContainer:has(select option[value="de-DE"]),
|
||||
.formSection:has(> label[for="selectLanguage"]),
|
||||
label[for="selectLanguage"],
|
||||
label[for="language"] {
|
||||
display: none !important;
|
||||
}
|
||||
</style><script>/* ARRFLIX-SHIM-BEGIN */
|
||||
(function(){
|
||||
var TITLE = 'ARRFLIX';
|
||||
var BARE_RE = /^Jellyfin$/i;
|
||||
/* === English-lockdown (synchronous, runs before Jellyfin bundle) ===
|
||||
Pins UI locale to en-US so the SPA never reads navigator.language
|
||||
or the user's stored preference. Belt-and-braces against:
|
||||
- localStorage keys the SPA reads on boot
|
||||
- navigator.language / navigator.languages getters
|
||||
- fetch / XHR Accept-Language header (best-effort; most browsers
|
||||
block JS from setting it, but Jellyfin sometimes does)
|
||||
- user-config save round-trip (rewrite UICulture → en-US before send) */
|
||||
try {
|
||||
var LS_KEYS = ['appLanguage','selectedlanguage','selectedlocale','language','locale','culture'];
|
||||
for (var i=0;i<LS_KEYS.length;i++){
|
||||
try { localStorage.setItem(LS_KEYS[i], 'en-US'); } catch(e){}
|
||||
}
|
||||
} catch(e){}
|
||||
try {
|
||||
var EN = ['en-US','en'];
|
||||
Object.defineProperty(Navigator.prototype, 'language', { get:function(){return 'en-US';}, configurable:true });
|
||||
Object.defineProperty(Navigator.prototype, 'languages', { get:function(){return EN.slice();}, configurable:true });
|
||||
} catch(e){
|
||||
/* fallback for engines that won't let us redefine on the prototype */
|
||||
try { Object.defineProperty(navigator, 'language', { get:function(){return 'en-US';}, configurable:true }); } catch(e2){}
|
||||
try { Object.defineProperty(navigator, 'languages', { get:function(){return ['en-US','en'];}, configurable:true }); } catch(e2){}
|
||||
}
|
||||
/* fetch wrapper: strip Accept-Language on outbound requests, and rewrite
|
||||
any user-config save body so UICulture is pinned to en-US. */
|
||||
try {
|
||||
if (window.fetch) {
|
||||
var _origFetch = window.fetch;
|
||||
window.fetch = function(input, init){
|
||||
try {
|
||||
init = init || {};
|
||||
/* strip Accept-Language if present on a plain object headers init */
|
||||
if (init.headers) {
|
||||
if (init.headers instanceof Headers) {
|
||||
try { init.headers.delete('Accept-Language'); } catch(e){}
|
||||
} else if (typeof init.headers === 'object') {
|
||||
for (var k in init.headers){ if (k && k.toLowerCase() === 'accept-language') { try { delete init.headers[k]; } catch(e){} } }
|
||||
}
|
||||
}
|
||||
/* rewrite user-config save: POST /Users/{id}/Configuration */
|
||||
var url = (typeof input === 'string') ? input : (input && input.url) || '';
|
||||
var method = (init.method || (input && input.method) || 'GET').toUpperCase();
|
||||
if (url && /\/Users\/[^/]+\/Configuration(\?|$)/.test(url) && method === 'POST' && init.body) {
|
||||
try {
|
||||
var body = init.body;
|
||||
if (typeof body === 'string') {
|
||||
var obj = JSON.parse(body);
|
||||
if (obj && typeof obj === 'object') {
|
||||
obj.UICulture = 'en-US';
|
||||
init.body = JSON.stringify(obj);
|
||||
}
|
||||
}
|
||||
} catch(e){}
|
||||
}
|
||||
} catch(e){}
|
||||
return _origFetch.call(this, input, init);
|
||||
};
|
||||
}
|
||||
} catch(e){}
|
||||
/* XHR wrapper: strip Accept-Language; rewrite user-config save body. */
|
||||
try {
|
||||
if (window.XMLHttpRequest) {
|
||||
var _open = XMLHttpRequest.prototype.open;
|
||||
var _setHeader = XMLHttpRequest.prototype.setRequestHeader;
|
||||
var _send = XMLHttpRequest.prototype.send;
|
||||
XMLHttpRequest.prototype.open = function(method, url){
|
||||
this.__arrflix_method = (method || 'GET').toUpperCase();
|
||||
this.__arrflix_url = url || '';
|
||||
return _open.apply(this, arguments);
|
||||
};
|
||||
XMLHttpRequest.prototype.setRequestHeader = function(name, value){
|
||||
if (name && String(name).toLowerCase() === 'accept-language') return;
|
||||
return _setHeader.apply(this, arguments);
|
||||
};
|
||||
XMLHttpRequest.prototype.send = function(body){
|
||||
try {
|
||||
if (this.__arrflix_url && /\/Users\/[^/]+\/Configuration(\?|$)/.test(this.__arrflix_url) && this.__arrflix_method === 'POST' && typeof body === 'string') {
|
||||
try {
|
||||
var obj = JSON.parse(body);
|
||||
if (obj && typeof obj === 'object') {
|
||||
obj.UICulture = 'en-US';
|
||||
body = JSON.stringify(obj);
|
||||
}
|
||||
} catch(e){}
|
||||
}
|
||||
} catch(e){}
|
||||
return _send.call(this, body);
|
||||
};
|
||||
}
|
||||
} catch(e){}
|
||||
/* Re-pin localStorage on every visibility change (SPA may rewrite on user save) */
|
||||
function pinLocale(){
|
||||
try {
|
||||
var L = ['appLanguage','selectedlanguage','selectedlocale','language','locale','culture'];
|
||||
for (var i=0;i<L.length;i++){ try { if (localStorage.getItem(L[i]) !== 'en-US') localStorage.setItem(L[i], 'en-US'); } catch(e){} }
|
||||
} catch(e){}
|
||||
}
|
||||
/* === end english-lockdown synchronous block === */
|
||||
function getFavicon(){
|
||||
var l = document.querySelector('link[rel="shortcut icon"], link[rel="icon"]');
|
||||
return l && l.href ? l.href : null;
|
||||
|
|
@ -51,18 +189,7 @@ a.btnSettings,
|
|||
}
|
||||
function nukeSettings(){
|
||||
try {
|
||||
// V2 (2026-05-08): Jellyfin web-bundle renders the drawer Settings link as
|
||||
// <a is="emby-linkbutton" class="navMenuOption ... btnSettings ..."
|
||||
// data-itemid="settings" href="#"> ... </a>
|
||||
// The href is literally "#"; the route is wired by JS click handler
|
||||
// keyed off data-itemid. The previous selector
|
||||
// a[href*="mypreferencesmenu"], [to*="mypreferencesmenu"]
|
||||
// matched ZERO elements in the live drawer (verified via headless probe).
|
||||
// Match by the stable class + data-itemid instead, and keep the legacy
|
||||
// href selector as a fallback for older bundles / future variants.
|
||||
var nodes = document.querySelectorAll(
|
||||
'a.btnSettings, [data-itemid="settings"], a[href*="mypreferencesmenu"], [to*="mypreferencesmenu"]'
|
||||
);
|
||||
var nodes = document.querySelectorAll('a[href*="mypreferencesmenu"], [to*="mypreferencesmenu"]');
|
||||
for (var i=0;i<nodes.length;i++){
|
||||
var el = nodes[i];
|
||||
var p = el.closest && (el.closest('li, .MuiListItem-root, [role="menuitem"]')) || el;
|
||||
|
|
@ -71,7 +198,7 @@ a.btnSettings,
|
|||
} catch(e){}
|
||||
}
|
||||
function start(){
|
||||
lockTitle(); lockFavicon(); nukeSettings();
|
||||
lockTitle(); lockFavicon(); nukeSettings(); pinLocale();
|
||||
try {
|
||||
var head = document.head || document.querySelector('head');
|
||||
if (head && window.MutationObserver) {
|
||||
|
|
@ -89,6 +216,7 @@ a.btnSettings,
|
|||
var fav = getFavicon();
|
||||
if (fav && fav.indexOf('data:image') !== 0) lockFavicon();
|
||||
nukeSettings();
|
||||
pinLocale();
|
||||
}, 1000);
|
||||
}
|
||||
if (document.readyState === 'loading') {
|
||||
|
|
|
|||
Loading…
Reference in a new issue