Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- CSS
- /* ==========================================================================
- 1. PINNED PANEL TRANSLUCENCY (HOVER & FOCUS DIFFERENTIATED)
- ========================================================================== */
- #zen-app-panel-root #zen-app-panel-slider {
- transition: opacity 0.22s ease, backdrop-filter 0.22s ease, background-color 0.22s ease !important;
- }
- /* Default unpinned panel */
- #zen-app-panel-root:not([data-pinned="true"]) #zen-app-panel-slider {
- background-color: color-mix(in srgb, var(--tabpanels-background-color, #1e1e24) 92%, transparent) !important;
- backdrop-filter: blur(20px) saturate(140%) !important;
- opacity: 1 !important;
- }
- /* Pinned + Hovered or Focused: readable translucent */
- #zen-app-panel-root[data-pinned="true"]:is(:hover, :focus-within) #zen-app-panel-slider {
- background-color: color-mix(in srgb, var(--tabpanels-background-color, #1e1e24) 65%, transparent) !important;
- backdrop-filter: blur(16px) saturate(130%) !important;
- opacity: 0.85 !important;
- }
- /* Pinned + Unfocused: high transparency */
- #zen-app-panel-root[data-pinned="true"]:not(:hover):not(:focus-within) #zen-app-panel-slider {
- background-color: color-mix(in srgb, var(--tabpanels-background-color, #1e1e24) 35%, transparent) !important;
- backdrop-filter: blur(10px) saturate(120%) !important;
- opacity: 0.45 !important;
- }
- /* Ensure pill menu is always 100% visible and interactive */
- #zen-app-panel-root #zen-app-panel-pill {
- opacity: 1 !important;
- pointer-events: auto !important;
- }
- /* ==========================================================================
- 2. OPPOSITE-SIDE DOCKING, PILL MENU & RESIZE STRIP
- ========================================================================== */
- #browser,
- #tabbrowser-tabpanels,
- #tabbrowser-tabbox,
- .browserSidebarContainer,
- :root #sidebar-box,
- :root #navigator-toolbox {
- max-width: unset !important;
- }
- /* Sidebar on Left -> Panel on Right */
- #zen-app-panel-root[data-panel-side="right"] {
- right: 12px !important;
- left: auto !important;
- }
- #zen-app-panel-root[data-panel-side="right"] .zen-app-resize-strip {
- left: -8px !important;
- right: auto !important;
- width: 14px !important;
- cursor: ew-resize !important;
- z-index: 30 !important;
- pointer-events: auto !important;
- }
- #zen-app-panel-root[data-panel-side="right"] #zen-app-panel-pill {
- left: 0px !important;
- right: auto !important;
- top: 50% !important;
- transform: translate(-100%, -50%) !important;
- pointer-events: auto !important;
- z-index: 40 !important;
- }
- #zen-app-panel-root[data-panel-side="right"] .zen-app-hover-zone {
- left: -24px !important;
- right: auto !important;
- pointer-events: auto !important;
- }
- /* Sidebar on Right -> Panel on Left */
- #zen-app-panel-root[data-panel-side="left"] {
- left: 12px !important;
- right: auto !important;
- }
- #zen-app-panel-root[data-panel-side="left"] .zen-app-resize-strip {
- right: -8px !important;
- left: auto !important;
- width: 14px !important;
- cursor: ew-resize !important;
- z-index: 30 !important;
- pointer-events: auto !important;
- }
- #zen-app-panel-root[data-panel-side="left"] #zen-app-panel-pill {
- right: 0px !important;
- left: auto !important;
- top: 50% !important;
- transform: translate(100%, -50%) !important;
- pointer-events: auto !important;
- z-index: 40 !important;
- }
- #zen-app-panel-root[data-panel-side="left"] .zen-app-hover-zone {
- right: -24px !important;
- left: auto !important;
- pointer-events: auto !important;
- }
- /* ==========================================================================
- 3. ISOLATE TABS & INDEPENDENT TILE STATES
- ========================================================================== */
- /* Reset tab-level filter and opacity so enclosed child tiles are never dimmed by tab state */
- .tabbrowser-tab:has(.zen-app-tile),
- .tabbrowser-tab[pending]:has(.zen-app-tile),
- .tabbrowser-tab[pending="true"]:has(.zen-app-tile),
- .tabbrowser-tab[zen-dormant="true"]:has(.zen-app-tile),
- .tabbrowser-tab[zen-essential="true"]:has(.zen-app-tile) {
- filter: none !important;
- opacity: 1 !important;
- }
- /* Shift pending/dormant dimming strictly to the tab's internal icon and stack */
- .tabbrowser-tab[pending]:has(.zen-app-tile) > .tab-stack,
- .tabbrowser-tab[pending="true"]:has(.zen-app-tile) > .tab-stack,
- .tabbrowser-tab[zen-dormant="true"]:has(.zen-app-tile) > .tab-stack,
- .tabbrowser-tab[pending]:has(.zen-app-tile) .tab-icon-image,
- .tabbrowser-tab[pending="true"]:has(.zen-app-tile) .tab-icon-image,
- .tabbrowser-tab[zen-dormant="true"]:has(.zen-app-tile) .tab-icon-image {
- filter: grayscale(100%) !important;
- opacity: 0.45 !important;
- }
- /* Selected/active tab restores full icon vibrancy */
- .tabbrowser-tab[selected="true"]:has(.zen-app-tile) .tab-icon-image,
- .tabbrowser-tab[selected="true"]:has(.zen-app-tile) .tab-stack {
- filter: none !important;
- opacity: 1 !important;
- }
- /* The tile state is 100% independent */
- .zen-app-tile {
- filter: none !important;
- opacity: 1 !important;
- }
- /* Unloaded / Dormant app panel: grayed out and semi-transparent */
- .zen-app-tile[data-unloaded="true"]:not([data-active="true"]),
- .zen-app-tile:not([data-loaded="true"]):not([data-active="true"]) {
- filter: grayscale(100%) !important;
- opacity: 0.35 !important;
- transition: opacity 0.2s ease, filter 0.2s ease !important;
- }
- .zen-app-tile[data-unloaded="true"]:not([data-active="true"]):hover,
- .zen-app-tile:not([data-loaded="true"]):not([data-active="true"]):hover {
- opacity: 0.85 !important;
- filter: grayscale(20%) !important;
- }
- /* Loaded or active app panel: FULL COLOR and 100% opacity regardless of tab state */
- .zen-app-tile[data-loaded="true"],
- .zen-app-tile[data-active="true"] {
- filter: none !important;
- opacity: 1 !important;
- }
- /* ==========================================================================
- 4. ENCLOSED CORNER-DOCKED BUTTONS (FLUSH BOTTOM-RIGHT EDGES)
- ========================================================================== */
- .tabbrowser-tab[pinned="true"],
- .tabbrowser-tab[zen-essential="true"],
- #zen-essentials-container .tabbrowser-tab {
- position: relative !important;
- }
- .tabbrowser-tab[pinned="true"] .zen-app-tile,
- .tabbrowser-tab[zen-essential="true"] .zen-app-tile,
- #zen-essentials-container .tabbrowser-tab .zen-app-tile,
- #zen-apps-sidebar-grid .zen-app-tile {
- position: absolute !important;
- right: 0px !important;
- bottom: 0px !important;
- width: 22px !important;
- height: 22px !important;
- min-width: 22px !important;
- min-height: 22px !important;
- max-width: 22px !important;
- max-height: 22px !important;
- padding: 0 !important;
- margin: 0 !important;
- display: flex !important;
- align-items: center !important;
- justify-content: center !important;
- border-radius: 6px 0 4px 0 !important;
- pointer-events: all !important;
- cursor: pointer !important;
- box-shadow: -1px -1px 3px rgba(0, 0, 0, 0.5) !important;
- background: rgba(22, 22, 28, 0.94) !important;
- border-top: 1px solid rgba(255, 255, 255, 0.15) !important;
- border-left: 1px solid rgba(255, 255, 255, 0.15) !important;
- border-right: none !important;
- border-bottom: none !important;
- transform: none !important;
- z-index: 99999 !important;
- }
- .zen-app-tile:hover {
- background: rgba(45, 45, 55, 0.98) !important;
- border-top-color: rgba(255, 255, 255, 0.35) !important;
- border-left-color: rgba(255, 255, 255, 0.35) !important;
- }
- /* 14px icon sizing */
- .zen-app-tile img,
- .zen-app-tile svg,
- .zen-app-tile image {
- width: 14px !important;
- height: 14px !important;
- min-width: 14px !important;
- min-height: 14px !important;
- max-width: 14px !important;
- max-height: 14px !important;
- pointer-events: none !important;
- }
- /* Notification badge */
- .zen-app-tile .zen-app-badge {
- top: -3px !important;
- right: -3px !important;
- min-width: 10px !important;
- height: 10px !important;
- font-size: 7px !important;
- line-height: 10px !important;
- padding: 0 2px !important;
- pointer-events: none !important;
- filter: none !important;
- opacity: 1 !important;
- }
- /* Hide fallback autohide elements */
- #zen-apps-sidebar-grid .zen-apps-autohide-dots,
- #zen-apps-sidebar-grid #zentral-apps-utility-section {
- display: none !important;
- }
- /* Ensure the pill menu always wins the stacking fight against corner-docked tiles */
- #zen-app-panel-root #zen-app-panel-pill {
- z-index: 999999 !important; /* was implicitly lower than .zen-app-tile's 99999 */
- }
- /* Make the resize strip's hit target forgiving even at very small panel widths */
- #zen-app-panel-root .zen-app-resize-strip {
- min-width: 14px !important;
- }
- /* Hide Expand/Restore: its width math only works for same-side docking, not our
- opposite-side setup, so it shrinks the panel instead of growing it. */
- #zen-app-panel-pill .zen-app-btn[title="Expand panel"],
- #zen-app-panel-pill .zen-app-btn[title="Restore panel"] {
- display: none !important;
- }
- JS
- /* ==========================================================================
- ZENTRAL EXTENSION: CORNER DOCK ON ESSENTIAL TABS, LOADED STATES,
- OPPOSITE-SIDE DOCKING, RELIABLE MIDDLE-CLICK UNLOAD
- (v3 - fixes the expand-width math for real, event-driven docking instead
- of polling, and proper cleanup so hot-reloads don't stack listeners)
- ========================================================================== */
- (() => {
- const patchZentralApps = () => {
- const apps = window.Zentral?.Apps || window.ZenApps || window.zentral?.apps;
- if (!apps) {
- setTimeout(patchZentralApps, 150);
- return;
- }
- // Idempotency guard: `window` persists across a Sine hot-reload of this
- // script, so without this a reload would double-wrap every method and
- // double-register every document-level listener below. The flag is
- // cleared again in the unload handler so a genuine reload still works.
- if (window.__zentralOppositeDockPatched) return;
- window.__zentralOppositeDockPatched = true;
- const proto = Object.getPrototypeOf(apps);
- const cleanupFns = [];
- const registerCleanup = (fn) => cleanupFns.push(fn);
- // Wraps/replaces a method on both the instance and its prototype, and
- // remembers how to put the original back on unload.
- const patchMethod = (name, makeWrapper) => {
- const orig = proto?.[name] || apps[name];
- const wrapped = makeWrapper(orig);
- apps[name] = wrapped;
- if (proto && proto !== Object.prototype) proto[name] = wrapped;
- registerCleanup(() => {
- apps[name] = orig;
- if (proto && proto !== Object.prototype) proto[name] = orig;
- });
- return orig;
- };
- const isSidebarOnRight = () => {
- try {
- if (typeof apps.isSidebarRight === "function") return apps.isSidebarRight();
- if (typeof proto?.isSidebarRight === "function") return proto.isSidebarRight.call(apps);
- } catch (_) {}
- return document.documentElement.getAttribute("zen-right-side") === "true" ||
- document.documentElement.getAttribute("zen-sidebar-right") === "true";
- };
- // 1. PREVENT RELOCATING TO TOP URL TOOLBAR
- patchMethod("isPhysicallySidebarCollapsed", () => () => false);
- // 2. INVERT DOCKING: OPPOSITE OF VERTICAL TAB BAR (sidebar placement only —
- // vertical-bar placement keeps its own original left/right logic untouched)
- patchMethod("isPanelAttachedToRight", (orig) => function () {
- if (typeof this.isPlacementVerticalBar === "function" && this.isPlacementVerticalBar()) {
- return typeof orig === "function" ? orig.call(this) : !isSidebarOnRight();
- }
- return !isSidebarOnRight();
- });
- // 3. Let the base mod's OWN positionPanel do all the real work — it already
- // reads isPanelAttachedToRight() above and correctly computes the
- // sidebar-width-aware, collapsed-aware left/right/top/bottom. We just
- // piggyback afterward to keep our custom pin-state attribute in sync.
- patchMethod("positionPanel", (orig) => function () {
- if (typeof orig === "function") {
- try { orig.call(this); } catch (_) {}
- }
- syncPinState();
- attachPillListeners();
- });
- // 4. FIX THE EXPAND-WIDTH MATH FOR OPPOSITE-SIDE DOCKING
- // The panel's left/right *position* already looks correct because our
- // CSS pins it with `!important` (right:12px / left:12px based on
- // data-panel-side) regardless of what JS computes. Width has no such
- // safety net, and the base toggleExpand() assumes the panel docks on
- // the SAME side as the real sidebar — with our inversion that produces
- // a negative/garbage width (clamped down to the ~280px minimum), which
- // is exactly the "expand shrinks the panel" bug. We let the original
- // run first (it correctly toggles state, updates the button icon, and
- // handles the vertical-bar placement mode, which isn't affected by our
- // inversion), then — only in the "just expanded" + sidebar-placement
- // case — recompute the correct width ourselves and overwrite it via
- // the public updateWidthVar(), using the same 12px gap our CSS uses so
- // the panel's open edge lines up exactly with where CSS pins the fixed
- // edge.
- patchMethod("toggleExpand", (orig) => function () {
- if (typeof orig === "function") {
- try { orig.call(this); } catch (_) {}
- }
- if (typeof this.isPlacementVerticalBar === "function" && this.isPlacementVerticalBar()) return;
- const pill = document.getElementById("zen-app-panel-pill");
- const expandBtn = pill?.querySelector(
- ".zen-app-btn[title='Restore panel'], .zen-app-btn[title='Expand panel']"
- );
- const justExpanded = expandBtn?.title === "Restore panel";
- if (!justExpanded) return; // restoring uses a cached width, which is side-independent and already correct
- const gap = 12;
- const sidebarEl = document.getElementById("sidebar-box") ||
- document.getElementById("sidebar-container") ||
- document.getElementById("vertical-tabs");
- const sRect = sidebarEl?.getBoundingClientRect();
- if (!sRect || sRect.width === 0) return; // no measurable sidebar; leave whatever the base computed
- let fullWidth = this.isPanelAttachedToRight()
- ? (window.innerWidth - gap) - (sRect.right + gap) // panel on right, sidebar on left: fill the gap between them
- : (sRect.left - gap) - gap; // panel on left, sidebar on right: fill the gap between them
- fullWidth = Math.max(280, Math.min(fullWidth, window.innerWidth - 100));
- if (typeof this.updateWidthVar === "function") this.updateWidthVar(fullWidth);
- });
- const syncPinState = () => {
- const root = document.getElementById("zen-app-panel-root");
- if (!root) return;
- const pinBtn = document.querySelector(
- "#zen-app-panel-pill .zen-app-btn[title*='Pin'], #zen-app-panel-pill .zen-app-btn[title*='pin']"
- );
- const isPinned = pinBtn?.getAttribute("data-pinned") === "true";
- root.setAttribute("data-pinned", isPinned ? "true" : "false");
- };
- // We don't intercept any pill button clicks (expand/pin/refresh/close/grab
- // all already work correctly on their own) — just piggyback to resync our
- // custom pin-state attribute a moment after any interaction with the pill.
- const attachPillListeners = () => {
- const pill = document.getElementById("zen-app-panel-pill");
- if (!pill || pill._listenersAttached) return;
- pill._listenersAttached = true;
- const handler = () => setTimeout(syncPinState, 50);
- pill.addEventListener("click", handler);
- registerCleanup(() => pill.removeEventListener("click", handler));
- };
- // === LEFT-CLICK: don't let it also select the essential tab underneath ===
- const tileMousedownGuard = (e) => {
- if (e.button !== 0) return;
- if (e.target.closest?.(".zen-app-tile[data-app-id]")) e.stopPropagation();
- };
- document.addEventListener("mousedown", tileMousedownGuard, { capture: true });
- registerCleanup(() => document.removeEventListener("mousedown", tileMousedownGuard, { capture: true }));
- // === RELIABLE MIDDLE-CLICK UNLOAD ===============================================
- // The base tile already fully implements left-click (toggle panel) and
- // right-click (context menu) — we must not add handlers for those or we'd
- // double-fire them. The only real gap is middle click: the base tile
- // ignores every button except 0, so it falls through to the tab
- // underneath. Zen/Firefox's "middle-click closes tab" is wired at the
- // chrome level via a plain "click" event (not just "auxclick"), so we
- // intercept all three at capture phase on `document` — the earliest
- // possible point — to reliably win the race regardless of event type or
- // where the tab's own handler is attached.
- const unloadApp = (appId) => {
- if (!appId) return;
- if (typeof apps.closeApp === "function") {
- try { apps.closeApp(appId); } catch (_) {}
- }
- document.querySelectorAll(`.zen-app-tile[data-app-id="${appId}"] .zen-app-badge`)
- .forEach((b) => b.remove());
- };
- const middleClickHandlers = {};
- ["mousedown", "click", "auxclick"].forEach((type) => {
- const handler = (e) => {
- if (e.button !== 1) return;
- const tile = e.target.closest?.(".zen-app-tile[data-app-id]");
- if (!tile) return;
- e.stopImmediatePropagation();
- e.preventDefault();
- if (type !== "mousedown") unloadApp(tile.getAttribute("data-app-id"));
- };
- middleClickHandlers[type] = handler;
- document.addEventListener(type, handler, { capture: true });
- registerCleanup(() => document.removeEventListener(type, handler, { capture: true }));
- });
- // === STABLE ID-BASED DOCKING TO ESSENTIAL TABS ===================================
- // Tiles are re-parented from the sidebar/vertical-bar grid into the
- // corresponding essential (pinned) tab, once per app-id, remembered via a
- // data attribute on the TAB (not the tile, since renderGrid() recreates
- // tile elements from scratch on every re-render — the assignment must
- // survive that).
- const dockTilesToEssentialTabs = () => {
- const essentialTabsRaw = Array.from(document.querySelectorAll(
- "#zen-essentials-container .tabbrowser-tab, " +
- "#zen-pinned-tab-container .tabbrowser-tab, " +
- "#zen-essentials .tabbrowser-tab, " +
- "#tabbrowser-tabs[has-pinned-tabs] .tabbrowser-tab[pinned='true'], " +
- ".tabbrowser-tab[pinned='true']"
- ));
- // Only trust elements that are genuinely registered tabs. Zen's
- // split-view feature creates visual clone(s) of a pinned tab for the
- // split preview; those aren't members of gBrowser.tabs. Cloning
- // duplicates whatever was inside the tab at clone time — including our
- // docked tile — which is what produced the "2nd Zentral icon" bug.
- const liveTabs = (typeof gBrowser !== "undefined" && gBrowser?.tabs)
- ? Array.from(gBrowser.tabs)
- : null;
- const essentialTabs = liveTabs
- ? essentialTabsRaw.filter((t) => liveTabs.includes(t))
- : essentialTabsRaw;
- const tiles = Array.from(document.querySelectorAll(".zen-app-tile[data-app-id]"));
- if (!tiles.length || !essentialTabs.length) return;
- // Any tile still sitting inside a NON-live tab (a split-view clone) is
- // a duplicate — remove it outright.
- essentialTabsRaw.forEach((tab) => {
- if (!essentialTabs.includes(tab)) {
- tab.querySelectorAll(".zen-app-tile[data-app-id]").forEach((t) => t.remove());
- }
- });
- // Global de-dupe across all real tabs: an app-id may only have ONE
- // tile, in ONE tab, anywhere.
- const seenAppIds = new Set();
- essentialTabs.forEach((tab) => {
- Array.from(tab.querySelectorAll(".zen-app-tile[data-app-id]")).forEach((t) => {
- const id = t.getAttribute("data-app-id");
- if (!id) return;
- if (seenAppIds.has(id)) t.remove();
- else seenAppIds.add(id);
- });
- });
- const tabsByAssignedId = new Map();
- essentialTabs.forEach((tab) => {
- const assigned = tab.getAttribute("data-zentral-assigned-app");
- if (assigned) tabsByAssignedId.set(assigned, tab);
- });
- tiles.forEach((tile) => {
- const id = tile.getAttribute("data-app-id");
- if (!id) return;
- const currentParentTab = tile.closest(".tabbrowser-tab");
- if (
- currentParentTab &&
- essentialTabs.includes(currentParentTab) &&
- currentParentTab.getAttribute("data-zentral-assigned-app") === id
- ) {
- return; // already correctly docked
- }
- const home = tabsByAssignedId.get(id);
- if (home) {
- if (tile.parentNode !== home) home.appendChild(tile);
- return;
- }
- // First time we've ever seen this app-id: claim the first free tab
- // and remember it permanently, so it's never re-decided by array
- // position again.
- const freeTab = essentialTabs.find((t) => !t.getAttribute("data-zentral-assigned-app"));
- if (freeTab) {
- freeTab.setAttribute("data-zentral-assigned-app", id);
- freeTab.appendChild(tile);
- }
- });
- };
- // === LOADED / DORMANT VISUAL STATE (custom, not part of base) ====================
- const syncTileLoadedStates = () => {
- const panelSlider = document.getElementById("zen-app-panel-slider");
- const loadedAppIds = new Set();
- if (panelSlider) {
- panelSlider.querySelectorAll("browser[data-zentral-app-id]").forEach((b) => {
- const id = b.getAttribute("data-zentral-app-id");
- if (id) loadedAppIds.add(id);
- });
- }
- document.querySelectorAll(".zen-app-tile[data-app-id]").forEach((tile) => {
- const id = tile.getAttribute("data-app-id");
- const isLoaded = id ? loadedAppIds.has(id) : false;
- tile.setAttribute("data-loaded", isLoaded ? "true" : "false");
- tile.setAttribute("data-unloaded", isLoaded ? "false" : "true");
- // data-active is already maintained live by the base mod's own
- // openPanel()/closePanel() across every .zen-app-tile[data-app-id] in
- // the document, so we deliberately don't touch it here.
- });
- dockTilesToEssentialTabs();
- };
- // Event-driven docking instead of a 1.5s poll: react to pinned/essential
- // tab changes as they happen, debounced so a burst of mutations (e.g.
- // dragging several tabs around) only triggers one re-sync.
- let dockDebounceTimer = null;
- const scheduleDock = (delay = 60) => {
- if (dockDebounceTimer) clearTimeout(dockDebounceTimer);
- dockDebounceTimer = setTimeout(() => {
- dockDebounceTimer = null;
- syncTileLoadedStates();
- }, delay);
- };
- registerCleanup(() => { if (dockDebounceTimer) clearTimeout(dockDebounceTimer); });
- const tabStripEl = document.getElementById("tabbrowser-tabs");
- if (tabStripEl) {
- const tabStripObserver = new MutationObserver(() => scheduleDock(60));
- tabStripObserver.observe(tabStripEl, {
- childList: true,
- subtree: true,
- attributes: true,
- attributeFilter: ["pinned", "zen-essential"]
- });
- registerCleanup(() => tabStripObserver.disconnect());
- }
- // Safety-net fallback for anything an event/observer might miss, at a
- // much lower cost than the old 1.5s poll.
- const fallbackInterval = setInterval(syncTileLoadedStates, 4000);
- registerCleanup(() => clearInterval(fallbackInterval));
- // Tag each app browser with its app id so "loaded" state can be detected
- // via the DOM (the real per-app browser map is a private class field we
- // can't read from outside).
- patchMethod("getOrCreateAppBrowser", (orig) => function (app) {
- const res = orig.call(this, app);
- const b = res?.browser || (res instanceof Element ? res : null);
- const id = app?.id || app;
- if (b && id) b.setAttribute("data-zentral-app-id", id);
- setTimeout(syncTileLoadedStates, 50);
- return res;
- });
- patchMethod("closeApp", (orig) => function (appId) {
- orig.call(this, appId);
- setTimeout(syncTileLoadedStates, 50);
- });
- patchMethod("renderGrid", (orig) => function () {
- orig.call(this);
- setTimeout(syncTileLoadedStates, 50);
- });
- setTimeout(syncTileLoadedStates, 200);
- // === CLEANUP / UNLOAD ============================================================
- // Without this, a Sine hot-reload during development would leave every
- // wrapped method and every document-level listener above stacked
- // permanently, since `window` (and therefore `apps`) survives a reload.
- const performPatchUnload = () => {
- cleanupFns.forEach((fn) => { try { fn(); } catch (_) {} });
- cleanupFns.length = 0;
- window.__zentralOppositeDockPatched = false;
- };
- if (typeof window.addUnloadListener === "function") {
- window.addUnloadListener(performPatchUnload);
- } else if (typeof UC_API !== "undefined" && UC_API.addUnloadListener) {
- UC_API.addUnloadListener(performPatchUnload);
- }
- window.addEventListener("unload", performPatchUnload, { once: true });
- };
- if (document.readyState === "complete") {
- patchZentralApps();
- } else {
- window.addEventListener("load", patchZentralApps, { once: true });
- }
- })();
Advertisement
Add Comment
Please, Sign In to add comment