feat(theseus/chrome): Ariadne's Thread registry menu, address-bar overflow fix, full-width link pill
- Link-status pill: it measured its own width inside a view already capped at 100 px, so it could never grow and long hrefs were cut short. An off-screen twin now reports the natural width; main caps it to the tab area (never under the sidebar) and the pill ellipsises past that. - Address bar at narrow widths: the URL input's intrinsic minimum width pushed the registry chips and the star out past the bar. #url now has min-width: 0 and the trailing controls are fixed-size flex items. - The BCDN/ICANN segmented chips are replaced by one Ariadne's Thread icon (spiral + tail) at the end of the bar: acid when served from BCDN, blue for ICANN, caret when the name exists on both. Click opens a native menu (registry-menu-popup): switch registry, remember per name / per TLD, forget choices, collision policy, and a jump to the Plug-ins settings section. Reuses the existing switch / remember / policy paths (collision-switch body extracted to switchRegistry, open-settings to openSettingsTab). preload's openSettings now forwards a section slug.
This commit is contained in:
parent
c9a3db26ce
commit
8a99c0959c
4 changed files with 119 additions and 50 deletions
80
chrome.html
80
chrome.html
|
|
@ -242,27 +242,27 @@
|
||||||
.secbadge.insecure { color: #f6768a; }
|
.secbadge.insecure { color: #f6768a; }
|
||||||
.secbadge.warn { color: #f6ad55; } /* kept for legacy call sites */
|
.secbadge.warn { color: #f6ad55; } /* kept for legacy call sites */
|
||||||
input { padding: 7px 6px; border-radius: 999px; border: none; background: transparent; color: inherit; font-size: 13.5px; outline: none; }
|
input { padding: 7px 6px; border-radius: 999px; border: none; background: transparent; color: inherit; font-size: 13.5px; outline: none; }
|
||||||
#url { flex: 1; }
|
/* min-width: 0 is what keeps the trailing controls INSIDE the pill. An
|
||||||
/* Registry indicator at the END of the address bar. Two shapes:
|
<input> has an intrinsic minimum (~20 characters) that a flex item
|
||||||
- Toggle (segmented control) when this host could be served by EITHER
|
won't shrink below by default, so on a narrow window the input held
|
||||||
registry — a single pill split in half, active side filled, inactive
|
its width and pushed the registry button and the star out past the
|
||||||
side transparent and clickable to switch.
|
bar's right edge. */
|
||||||
- Single pill when there's no alternative (pure ICANN or a BCNR-unique TLD).*/
|
#url { flex: 1 1 0; min-width: 0; }
|
||||||
#reg { display: inline-flex; align-items: center; }
|
.secbadge, .pwchip, .star, .regbtn { flex: none; }
|
||||||
.reg { font-size: 10.5px; letter-spacing: .03em; padding: 2px 10px; white-space: nowrap;
|
/* Ariadne's Thread button at the END of the address bar. One icon in
|
||||||
cursor: default; user-select: none; transition: background .12s, color .12s, border-color .12s; }
|
place of the old BCDN/ICANN chips: tinted acid when the page is served
|
||||||
/* Single-chip form (non-collision hosts) */
|
from BCDN, blue for ICANN, neutral otherwise. A small caret marks a
|
||||||
.reg.single { border-radius: 999px; border: 1px solid; }
|
collision candidate (the name exists on both registries). Click opens
|
||||||
.reg.single.bcdn.active { background: rgb(from var(--acid) r g b / .15); color: var(--acid); border-color: color-mix(in srgb, var(--acid) 33%, transparent); }
|
a native menu: switch registry, remember the choice, collision policy,
|
||||||
.reg.single.icann.active { background: rgba(76,158,255,.15); color: #4c9eff; border-color: #4c9eff55; }
|
resolver settings. */
|
||||||
/* Toggle (segmented control) form for collision candidates */
|
.regbtn { display: inline-flex; align-items: center; gap: 1px; height: 24px; padding: 0 3px 0 4px; border-radius: 6px;
|
||||||
.tog { display: inline-flex; border-radius: 999px; overflow: hidden; border: 1px solid #ffffff26; }
|
border: none; background: transparent; cursor: pointer; color: var(--dim); }
|
||||||
.tog .reg { border: 0; }
|
.regbtn:hover { background: var(--line2); }
|
||||||
.tog .reg + .reg { border-left: 1px solid #ffffff26; }
|
.regbtn svg { width: 18px; height: 18px; }
|
||||||
.tog .reg.active.bcdn { background: rgb(from var(--acid) r g b / .18); color: var(--acid); }
|
.regbtn.bcdn { color: var(--acid); }
|
||||||
.tog .reg.active.icann { background: rgba(76,158,255,.18); color: #4c9eff; }
|
.regbtn.icann { color: #4c9eff; }
|
||||||
.tog .reg.switch { background: transparent; color: #7b8494; cursor: pointer; }
|
.regbtn .cv { display: none; font-size: 8px; color: var(--dim); }
|
||||||
.tog .reg.switch:hover { color: #e5e7eb; background: rgba(255,255,255,.05); }
|
.regbtn.cand .cv { display: inline; }
|
||||||
.star { border: none; background: transparent; cursor: pointer; color: var(--dim); font-size: 15px; padding: 2px 4px; border-radius: 6px; }
|
.star { border: none; background: transparent; cursor: pointer; color: var(--dim); font-size: 15px; padding: 2px 4px; border-radius: 6px; }
|
||||||
.star:hover { background: var(--line2); color: var(--ink); } .star.on { color: #ffd23d; }
|
.star:hover { background: var(--line2); color: var(--ink); } .star.on { color: #ffd23d; }
|
||||||
/* Update-available chip — shows when the releases manifest names a newer
|
/* Update-available chip — shows when the releases manifest names a newer
|
||||||
|
|
@ -410,7 +410,12 @@
|
||||||
</svg>
|
</svg>
|
||||||
</button>
|
</button>
|
||||||
<input id="url" placeholder="Ask a search engine or enter web address" spellcheck="false">
|
<input id="url" placeholder="Ask a search engine or enter web address" spellcheck="false">
|
||||||
<span class="reg" id="reg" hidden></span>
|
<button class="regbtn" id="reg" title="Ariadne's Thread" hidden>
|
||||||
|
<!-- Ariadne's thread: a spiral of semicircles (radii 1 → 5,
|
||||||
|
centres alternating 8 / 9) unwinding into a loose tail. -->
|
||||||
|
<svg viewBox="0 0 16 16" aria-hidden="true"><path d="M9 7.5 A1 1 0 0 0 7 7.5 A2 2 0 0 0 11 7.5 A3 3 0 0 0 5 7.5 A4 4 0 0 0 13 7.5 A5 5 0 0 0 3 7.5 C3 11.2 6 14.6 13.6 13.4" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round"/></svg>
|
||||||
|
<span class="cv">▾</span>
|
||||||
|
</button>
|
||||||
<button class="pwchip" id="pwchip" title="Fill password for this site" hidden><span id="pwchipCount"></span><svg viewBox="0 0 16 16" aria-hidden="true"><circle cx="6" cy="9" r="3.2" fill="none" stroke="currentColor" stroke-width="1.5"/><path d="M8.8 8.6 L14 8.6 M13.2 8.6 L13.2 11 M11.5 8.6 L11.5 10.4" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/></svg></button>
|
<button class="pwchip" id="pwchip" title="Fill password for this site" hidden><span id="pwchipCount"></span><svg viewBox="0 0 16 16" aria-hidden="true"><circle cx="6" cy="9" r="3.2" fill="none" stroke="currentColor" stroke-width="1.5"/><path d="M8.8 8.6 L14 8.6 M13.2 8.6 L13.2 11 M11.5 8.6 L11.5 10.4" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/></svg></button>
|
||||||
<button class="star" id="star" title="Save this page">☆</button>
|
<button class="star" id="star" title="Save this page">☆</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -1060,30 +1065,27 @@
|
||||||
else if (d.kind === "error") { cls = "secbadge insecure"; title = "Resolution error"; }
|
else if (d.kind === "error") { cls = "secbadge insecure"; title = "Resolution error"; }
|
||||||
b.className = cls; b.title = title;
|
b.className = cls; b.title = title;
|
||||||
}
|
}
|
||||||
|
// Ariadne's Thread button: tint = registry serving the page, caret = the
|
||||||
|
// name exists on both registries. The menu itself is native (main.js
|
||||||
|
// registry-menu-popup) so it can't be clipped by the chrome view.
|
||||||
function setReg(d) {
|
function setReg(d) {
|
||||||
const r = $("reg");
|
const r = $("reg");
|
||||||
if (!d || d.kind === "home") { r.hidden = true; r.innerHTML = ""; return; }
|
if (!d || d.kind === "home") { r.hidden = true; r.className = "regbtn"; r.title = "Ariadne's Thread"; return; }
|
||||||
// Keep chips visible during "resolving" so the switch feels like a toggle,
|
// Keep the last state during "resolving" so a switch reads as a toggle,
|
||||||
// not a disappear-and-reappear (user complaint 2026-08-02).
|
// not a disappear-and-reappear (user complaint 2026-08-02).
|
||||||
if (d.kind === "resolving") { r.hidden = false; return; }
|
if (d.kind === "resolving") { r.hidden = false; return; }
|
||||||
r.hidden = false;
|
r.hidden = false;
|
||||||
// Two chips (toggle) when this host is a collision candidate — could be
|
|
||||||
// BCDN OR ICANN. Active one is coloured; inactive is greyed + clickable.
|
|
||||||
const isCand = !!d.tld && (d.kind === "ok" || d.kind === "web") && d.bcnrNativeTld === false;
|
const isCand = !!d.tld && (d.kind === "ok" || d.kind === "web") && d.bcnrNativeTld === false;
|
||||||
const active = d.kind === "ok" ? "bcdn" : d.kind === "web" ? "icann" : null;
|
const on = d.kind === "ok" ? "bcdn" : d.kind === "web" ? "icann" : "";
|
||||||
if (isCand) {
|
r.className = "regbtn" + (on ? " " + on : "") + (isCand ? " cand" : "");
|
||||||
const bcdn = `<span class="reg bcdn ${active === "bcdn" ? "active" : "switch"}" data-c="bcdn" title="${active === "bcdn" ? "Serving from BCDN" : "Switch to BCDN"}">BCDN</span>`;
|
r.title = on === "bcdn" ? "Serving from BCDN" + (isCand ? " · also exists on ICANN" : "") + " — click for options"
|
||||||
const icann = `<span class="reg icann ${active === "icann" ? "active" : "switch"}" data-c="icann" title="${active === "icann" ? "Serving from ICANN" : "Switch to ICANN"}">ICANN</span>`;
|
: on === "icann" ? "Serving from ICANN" + (isCand ? " · also exists on BCDN" : "") + " — click for options"
|
||||||
r.innerHTML = `<span class="tog">${bcdn}${icann}</span>`;
|
: "Ariadne's Thread — click for options";
|
||||||
for (const chip of r.querySelectorAll(".reg.switch")) {
|
|
||||||
chip.onclick = () => T.collisionSwitch({ choice: chip.dataset.c, remember: "no" });
|
|
||||||
}
|
|
||||||
} else if (d.kind === "web") {
|
|
||||||
r.innerHTML = `<span class="reg single icann active">ICANN</span>`;
|
|
||||||
} else {
|
|
||||||
r.innerHTML = `<span class="reg single bcdn active">BCDN</span>`;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
$("reg").onclick = () => {
|
||||||
|
const b = $("reg").getBoundingClientRect();
|
||||||
|
if (T.registryMenuPopup) T.registryMenuPopup({ x: Math.round(b.left), y: Math.round(b.bottom + 2) }).catch(() => {});
|
||||||
|
};
|
||||||
|
|
||||||
// ---- Tor ----
|
// ---- Tor ----
|
||||||
let torShown = false;
|
let torShown = false;
|
||||||
|
|
|
||||||
|
|
@ -8,15 +8,24 @@
|
||||||
color: #b9c2d0; font-size: 11.5px; padding: 4px 10px 4px 8px;
|
color: #b9c2d0; font-size: 11.5px; padding: 4px 10px 4px 8px;
|
||||||
display: inline-block; max-width: 100%; overflow: hidden;
|
display: inline-block; max-width: 100%; overflow: hidden;
|
||||||
text-overflow: ellipsis; white-space: nowrap; direction: ltr; }
|
text-overflow: ellipsis; white-space: nowrap; direction: ltr; }
|
||||||
|
/* Off-screen twin used only to measure the URL's natural width. The
|
||||||
|
visible pill is capped at 100% of this view, so measuring IT would
|
||||||
|
just report whatever width the view already has — the pill could
|
||||||
|
never grow past its first 100px and long links got cut short. */
|
||||||
|
.measure { position: absolute; left: 0; top: 0; visibility: hidden; max-width: none; }
|
||||||
@media (prefers-color-scheme: light) {
|
@media (prefers-color-scheme: light) {
|
||||||
.pill { background: #ffffff; color: #3c4453; border-color: rgba(0,0,0,.15); }
|
.pill { background: #ffffff; color: #3c4453; border-color: rgba(0,0,0,.15); }
|
||||||
}
|
}
|
||||||
</style></head>
|
</style></head>
|
||||||
<body><span class="pill" id="p"></span>
|
<body><span class="pill" id="p"></span><span class="pill measure" id="m" aria-hidden="true"></span>
|
||||||
<script>
|
<script>
|
||||||
const p = document.getElementById("p");
|
const p = document.getElementById("p");
|
||||||
|
const m = document.getElementById("m");
|
||||||
window.linkstatus.onUrl((url) => {
|
window.linkstatus.onUrl((url) => {
|
||||||
p.textContent = String(url || "");
|
const s = String(url || "");
|
||||||
requestAnimationFrame(() => { try { window.linkstatus.resize(p.offsetWidth + 2, p.offsetHeight + 2); } catch (e) {} });
|
p.textContent = s; m.textContent = s;
|
||||||
|
// Ask main for the natural width; main clamps to the tab area and the
|
||||||
|
// visible pill ellipsises whatever doesn't fit.
|
||||||
|
requestAnimationFrame(() => { try { window.linkstatus.resize(m.offsetWidth + 2, p.offsetHeight + 2); } catch (e) {} });
|
||||||
});
|
});
|
||||||
</script></body></html>
|
</script></body></html>
|
||||||
|
|
|
||||||
69
main.js
69
main.js
|
|
@ -2118,7 +2118,11 @@ function positionPwFill() {
|
||||||
function positionLinkStatus() {
|
function positionLinkStatus() {
|
||||||
if (!linkStatus || !win) return;
|
if (!linkStatus || !win) return;
|
||||||
const { width, height } = win.getContentBounds();
|
const { width, height } = win.getContentBounds();
|
||||||
const w = Math.min(Math.max(120, linkStatusW), Math.max(200, width - 20));
|
// The pill may grow to (almost) the full width of the tab area — long
|
||||||
|
// URLs stay readable — and ellipsises past that. It never runs under
|
||||||
|
// the sidebar.
|
||||||
|
const tabW = Math.max(0, width - (sidebarVisible ? sidebarW : 0));
|
||||||
|
const w = Math.min(Math.max(120, linkStatusW), Math.max(200, tabW - 16));
|
||||||
const h = Math.max(20, linkStatusH);
|
const h = Math.max(20, linkStatusH);
|
||||||
linkStatus.setBounds({ x: 0, y: Math.max(0, height - h), width: w, height: h });
|
linkStatus.setBounds({ x: 0, y: Math.max(0, height - h), width: w, height: h });
|
||||||
}
|
}
|
||||||
|
|
@ -3618,10 +3622,11 @@ ipcMain.handle("reload", (_e, hard) => {
|
||||||
});
|
});
|
||||||
ipcMain.handle("stop", () => { const t = activeTab(); try { t?.view.webContents.stop(); } catch {} setLoading(t, false); });
|
ipcMain.handle("stop", () => { const t = activeTab(); try { t?.view.webContents.stop(); } catch {} setLoading(t, false); });
|
||||||
ipcMain.handle("toggle-tor", () => { torState === "off" ? startTor() : stopTor(); });
|
ipcMain.handle("toggle-tor", () => { torState === "off" ? startTor() : stopTor(); });
|
||||||
ipcMain.handle("open-settings", (_e, section) => {
|
// Open (or focus) the Settings tab. Optional section slug ("passwords",
|
||||||
// Optional section slug ("passwords", "addons", …) lands directly on
|
// "addons", …) lands directly on that sidebar entry when the page loads.
|
||||||
// that sidebar entry when the page loads. settings.html watches for a
|
// settings.html watches for a fragment on load and an IPC message when
|
||||||
// fragment on load and an IPC message when already loaded.
|
// already loaded.
|
||||||
|
function openSettingsTab(section) {
|
||||||
const slug = typeof section === "string" && /^[a-z0-9-]{1,32}$/i.test(section) ? section.toLowerCase() : "";
|
const slug = typeof section === "string" && /^[a-z0-9-]{1,32}$/i.test(section) ? section.toLowerCase() : "";
|
||||||
const ex = tabs.find((t) => t.settings);
|
const ex = tabs.find((t) => t.settings);
|
||||||
if (ex) {
|
if (ex) {
|
||||||
|
|
@ -3630,7 +3635,8 @@ ipcMain.handle("open-settings", (_e, section) => {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
createTab(null, { settings: true, settingsSection: slug });
|
createTab(null, { settings: true, settingsSection: slug });
|
||||||
});
|
}
|
||||||
|
ipcMain.handle("open-settings", (_e, section) => openSettingsTab(section));
|
||||||
ipcMain.handle("toggle-site-info", (_e, rect) => {
|
ipcMain.handle("toggle-site-info", (_e, rect) => {
|
||||||
if (popVisible) return showPopover(false);
|
if (popVisible) return showPopover(false);
|
||||||
if (rect) popPos = { x: Math.round(rect.x), y: Math.round(rect.y) };
|
if (rect) popPos = { x: Math.round(rect.x), y: Math.round(rect.y) };
|
||||||
|
|
@ -3642,7 +3648,8 @@ ipcMain.handle("popover-resize", (_e, h) => { popH = Math.max(90, Math.min(380,
|
||||||
// ---- Collision-mode: per-tab live switcher + policy control -----------------
|
// ---- Collision-mode: per-tab live switcher + policy control -----------------
|
||||||
// Flip the active tab between BCNR and ICANN for its current host, optionally
|
// Flip the active tab between BCNR and ICANN for its current host, optionally
|
||||||
// remembering the choice per-name / per-TLD (like the OS "Open with…" flow).
|
// remembering the choice per-name / per-TLD (like the OS "Open with…" flow).
|
||||||
ipcMain.handle("collision-switch", async (_e, arg) => {
|
ipcMain.handle("collision-switch", (_e, arg) => switchRegistry(arg));
|
||||||
|
async function switchRegistry(arg) {
|
||||||
const t = activeTab(); if (!t?.prov?.host) return null;
|
const t = activeTab(); if (!t?.prov?.host) return null;
|
||||||
const host = t.prov.host, tld = tldOf(host);
|
const host = t.prov.host, tld = tldOf(host);
|
||||||
// Accept both "bcdn" (client-facing product name) and "bcnr" (internal key).
|
// Accept both "bcdn" (client-facing product name) and "bcnr" (internal key).
|
||||||
|
|
@ -3683,6 +3690,54 @@ ipcMain.handle("collision-switch", async (_e, arg) => {
|
||||||
finally { t.internalNav = false; setLoading(t, false); }
|
finally { t.internalNav = false; setLoading(t, false); }
|
||||||
if (t.id === activeId) pushNav(t.prov);
|
if (t.id === activeId) pushNav(t.prov);
|
||||||
emitTabs();
|
emitTabs();
|
||||||
|
}
|
||||||
|
// Ariadne's Thread menu — the registry button at the end of the address
|
||||||
|
// bar. Replaces the BCDN/ICANN segmented chips: one icon that never
|
||||||
|
// overflows the bar, a native popup with the switch, the per-name / per-TLD
|
||||||
|
// memory, the collision policy, and a jump to the resolver settings.
|
||||||
|
ipcMain.handle("registry-menu-popup", (e, rect) => {
|
||||||
|
if (chrome && e.sender !== chrome.webContents) throw new Error("registry-menu-popup: untrusted sender");
|
||||||
|
const t = activeTab();
|
||||||
|
const prov = t?.prov || null;
|
||||||
|
const host = String(prov?.host || "");
|
||||||
|
const tld = String(prov?.tld || (host ? tldOf(host) : "") || "");
|
||||||
|
const onBcdn = prov?.kind === "ok";
|
||||||
|
const onIcann = prov?.kind === "web";
|
||||||
|
const isCand = !!tld && (onBcdn || onIcann) && !isBcnrNativeTld(tld);
|
||||||
|
const current = onBcdn ? "bcnr" : "icann";
|
||||||
|
const policyItem = (value, label) => ({
|
||||||
|
label, type: "radio", checked: settings.collisionPolicy === value,
|
||||||
|
click: () => { settings.collisionPolicy = value; saveSettings(); },
|
||||||
|
});
|
||||||
|
const template = [
|
||||||
|
{ label: host ? `Ariadne's Thread · ${host}` : "Ariadne's Thread", enabled: false },
|
||||||
|
{ type: "separator" },
|
||||||
|
{ label: "Serve from BCDN", type: "radio", checked: onBcdn, enabled: isCand || onBcdn,
|
||||||
|
click: () => { if (!onBcdn) switchRegistry({ choice: "bcnr", remember: "no" }).catch(() => {}); } },
|
||||||
|
{ label: "Serve from ICANN", type: "radio", checked: onIcann, enabled: isCand || onIcann,
|
||||||
|
click: () => { if (!onIcann) switchRegistry({ choice: "icann", remember: "no" }).catch(() => {}); } },
|
||||||
|
{ label: "Remember", enabled: !!host, submenu: [
|
||||||
|
{ label: host ? `Always open ${host} this way` : "Always open this site this way", enabled: isCand,
|
||||||
|
click: () => rememberCollision(host, tld, current, "name") },
|
||||||
|
{ label: tld ? `Always open .${tld} names this way` : "Always open this TLD this way", enabled: isCand,
|
||||||
|
click: () => rememberCollision(host, tld, current, "tld") },
|
||||||
|
{ type: "separator" },
|
||||||
|
{ label: "Forget remembered choices", click: () => { collisions = { byName: {}, byTld: {} }; saveCollisions(); } },
|
||||||
|
] },
|
||||||
|
{ type: "separator" },
|
||||||
|
{ label: "When a name exists on both registries", submenu: [
|
||||||
|
policyItem("bcnr-first", "BCDN first"),
|
||||||
|
policyItem("icann-first", "ICANN first"),
|
||||||
|
policyItem("soft", "Ask each time"),
|
||||||
|
] },
|
||||||
|
{ type: "separator" },
|
||||||
|
{ label: "Ariadne's Thread settings…", click: () => openSettingsTab("plugins") },
|
||||||
|
];
|
||||||
|
const popup = Menu.buildFromTemplate(template);
|
||||||
|
const chromeBounds = chrome ? chrome.getBounds() : { x: 0, y: 0 };
|
||||||
|
const x = Math.max(0, Math.round(chromeBounds.x + (rect?.x || 0)));
|
||||||
|
const y = Math.max(0, Math.round(chromeBounds.y + (rect?.y || 0)));
|
||||||
|
popup.popup({ window: win, x, y });
|
||||||
});
|
});
|
||||||
ipcMain.handle("collision-state", () => ({
|
ipcMain.handle("collision-state", () => ({
|
||||||
policy: settings.collisionPolicy,
|
policy: settings.collisionPolicy,
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,7 @@ contextBridge.exposeInMainWorld("theseus", {
|
||||||
reload: (hard) => ipcRenderer.invoke("reload", !!hard),
|
reload: (hard) => ipcRenderer.invoke("reload", !!hard),
|
||||||
stop: () => ipcRenderer.invoke("stop"),
|
stop: () => ipcRenderer.invoke("stop"),
|
||||||
toggleTor: () => ipcRenderer.invoke("toggle-tor"),
|
toggleTor: () => ipcRenderer.invoke("toggle-tor"),
|
||||||
openSettings: () => ipcRenderer.invoke("open-settings"),
|
openSettings: (section) => ipcRenderer.invoke("open-settings", section),
|
||||||
getSettings: () => ipcRenderer.invoke("settings-get"),
|
getSettings: () => ipcRenderer.invoke("settings-get"),
|
||||||
setSetting: (key, val) => ipcRenderer.invoke("settings-set", key, val),
|
setSetting: (key, val) => ipcRenderer.invoke("settings-set", key, val),
|
||||||
onSettingsUpdate: (cb) => ipcRenderer.on("settings-update", (_e, d) => cb(d)),
|
onSettingsUpdate: (cb) => ipcRenderer.on("settings-update", (_e, d) => cb(d)),
|
||||||
|
|
@ -87,6 +87,9 @@ contextBridge.exposeInMainWorld("theseus", {
|
||||||
// layering entirely. Pass the button's viewport-rect so main can anchor.
|
// layering entirely. Pass the button's viewport-rect so main can anchor.
|
||||||
toolbarMenuPopup: (addonId, rect) => ipcRenderer.invoke("toolbar-menu-popup", addonId, rect),
|
toolbarMenuPopup: (addonId, rect) => ipcRenderer.invoke("toolbar-menu-popup", addonId, rect),
|
||||||
tabContextMenuPopup: (tabId, rect) => ipcRenderer.invoke("tab-context-menu-popup", tabId, rect),
|
tabContextMenuPopup: (tabId, rect) => ipcRenderer.invoke("tab-context-menu-popup", tabId, rect),
|
||||||
|
// Ariadne's Thread menu behind the registry button at the end of the
|
||||||
|
// address bar (BCDN/ICANN switch, remembered choices, policy, settings).
|
||||||
|
registryMenuPopup: (rect) => ipcRenderer.invoke("registry-menu-popup", rect),
|
||||||
// Chrome listens so it can drop the "active" tint when the native menu
|
// Chrome listens so it can drop the "active" tint when the native menu
|
||||||
// dismisses (Esc, outside click, item picked — main fires all three).
|
// dismisses (Esc, outside click, item picked — main fires all three).
|
||||||
onToolbarMenuClosed: (cb) => ipcRenderer.on("toolbar-menu-closed", () => cb()),
|
onToolbarMenuClosed: (cb) => ipcRenderer.on("toolbar-menu-closed", () => cb()),
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue