← Commit history

v2.0.506 (insiders): Proven on AdomLapper: ext window sized to the full work area (2561x1529), thread tile on the taskbar, no CfT automated-testing bar, no launch-on-startup promo bar, four teal toolbar buttons on the ext lane and three on the CDP lane.

John Lauer ·320ddfbe5b ·1mo ago ·parent c404e37
2 files changed +170−2
src/extension/manifest.json+2−1
@@ -14,7 +14,8 @@     "storage",     "tabs",     "alarms",-    "scripting"+    "scripting",+    "system.display"   ],   "host_permissions": [     "http://127.0.0.1/*",
src/extension/worker.js+168−1
@@ -5,9 +5,26 @@ // an older worker painted. const CANDIDATE_PORTS = [64230, 8851]; +// pup STAGES port.json into this extension folder with the bridge's REAL port. Read it: a FRESH profile has+// empty chrome.storage, so without this the worker can only find pup when it happens to be on one of the two+// hardcoded candidates — which is why the extension never called home on ConfRoomROG (pup-bridge#109).+let _pkgPort = null;+let _pkgLane = '';   // only the extension-DRIVE browser gets a lane token in port.json+async function packagedPort() {+  if (_pkgPort !== null) return _pkgPort;+  try {+    const r = await fetch(chrome.runtime.getURL('port.json'), { cache: 'no-store' });+    const j = await r.json();+    _pkgPort = (j && j.port) || 0;+    _pkgLane = (j && j.lane) || '';+  } catch (e) { _pkgPort = 0; }+  return _pkgPort;+}+ async function findPort() {   const { pupPort } = await chrome.storage.local.get('pupPort');-  const ports = pupPort ? [pupPort, ...CANDIDATE_PORTS.filter(p => p !== pupPort)] : CANDIDATE_PORTS;+  const pkg = await packagedPort();+  const ports = [...new Set([pupPort, pkg, ...CANDIDATE_PORTS].filter(Boolean))];   for (const port of ports) {     try {       const r = await fetch(`http://127.0.0.1:${port}/health`, { signal: AbortSignal.timeout(1200) });@@ -90,3 +107,153 @@ chrome.commands.onCommand.addListener(async (command) => {     body: JSON.stringify(payload),   }).catch(() => {}); });++// ── extension-driven control channel (pup-bridge#109) ───────────────────────────────────+// Measured 2026-09-12: a Chrome carrying a CDP --remote-debugging-port is walled by several+// vendor edges (st.com and mouser RST at the HTTP/2 layer; arrow denies) while the SAME Chrome for+// Testing binary, same raw Chromium-148 brands, driven from this extension with NO debug port,+// loads all three. So pup can drive a window through here when CDP is the thing getting blocked.+// The worker polls pup for one command at a time and posts the result back.+let pupExtBusy = false;++async function pupExtTab(cmd) {+  if (cmd.tabId) { try { return await chrome.tabs.get(cmd.tabId); } catch (e) {} }+  if (cmd.windowId) {+    const inW = await chrome.tabs.query({ active: true, windowId: cmd.windowId });+    if (inW && inW[0]) return inW[0];+  }+  // Prefer a real page over the launcher's about:blank/newtab, then anything.+  const all = await chrome.tabs.query({});+  const real = all.filter((t) => t.url && /^https?:/.test(t.url));+  if (real.length) return real.find((t) => t.active) || real[real.length - 1];+  return all && all[0];+}++function pupExtWaitComplete(tabId, ms) {+  return new Promise((resolve) => {+    const done = () => { try { chrome.tabs.onUpdated.removeListener(h); } catch (e) {} resolve(); };+    const t = setTimeout(done, ms);+    const h = (id, info) => { if (id === tabId && info.status === 'complete') { clearTimeout(t); done(); } };+    chrome.tabs.onUpdated.addListener(h);+  });+}++let pupExtDiag = { tries: 0, pkg: null, lastErr: '' };+async function pupExtPoll() {+  if (pupExtBusy) return;+  pupExtDiag.tries++;+  pupExtDiag.pkg = await packagedPort();+  const found = await findPort();+  // Only the extension-driven browser serves this channel. A normal pup window loads the same extension+  // and must NEVER answer another thread's ext commands.+  if (!_pkgLane) return;+  if (!found) {+    // No pup reachable. Surface WHY on the action tooltip so a human (or a screenshot) can read it without+    // needing the very channel that is broken.+    try { chrome.action.setTitle({ title: `Adom Pup — CANNOT REACH BRIDGE (tries ${pupExtDiag.tries}, port.json ${pupExtDiag.pkg})` }); } catch (e) {}+    return;+  }+  let cmd = null;+  try {+    const r = await fetch(`http://127.0.0.1:${found.port}/extcmd?lane=${encodeURIComponent(_pkgLane)}`, { signal: AbortSignal.timeout(2500) });+    cmd = await r.json();+  } catch (e) { return; }+  if (!cmd || !cmd.id) return;+  pupExtBusy = true;+  let out = { id: cmd.id, ok: false, error: `unsupported action ${cmd.action}` };+  try {+    if (cmd.action === 'open') {+      const w = await chrome.windows.create({ url: cmd.url || 'about:blank', focused: false });+      const tab = w.tabs && w.tabs[0];+      if (tab) await pupExtWaitComplete(tab.id, cmd.waitMs || 30000);+      out = { id: cmd.id, ok: true, tabId: tab && tab.id, windowId: w.id };+    } else if (cmd.action === 'navigate') {+      const tab = await pupExtTab(cmd);+      await chrome.tabs.update(tab.id, { url: cmd.url });+      await pupExtWaitComplete(tab.id, cmd.waitMs || 30000);+      const t2 = await chrome.tabs.get(tab.id);+      out = { id: cmd.id, ok: true, tabId: tab.id, windowId: t2.windowId, url: t2.url, title: t2.title };+    } else if (cmd.action === 'eval') {+      const tab = await pupExtTab(cmd);+      const res = await chrome.scripting.executeScript({+        target: { tabId: tab.id }, world: 'MAIN',+        // AWAIT the value: an async expression returned a Promise, which stringified to "{}" and silently+        // swallowed every asynchronous probe (measured while tracing the Akamai sensor, 2026-09-12).+        func: async (src) => {+          try {+            const v = await eval(src);+            return typeof v === 'string' ? v : JSON.stringify(v);+          } catch (e) { return 'ERR:' + (e && e.message ? e.message : e); }+        },+        args: [cmd.expr || ''],+      });+      out = { id: cmd.id, ok: true, result: res && res[0] && res[0].result, tabId: tab.id, windowId: tab.windowId, url: tab.url };+    } else if (cmd.action === 'newtab') {+      // a TAB in the SAME window: the channel could only create new WINDOWS, so it could not show many+      // pages in one window the way the CDP lane does.+      const ref = await pupExtTab(cmd);+      const t = await chrome.tabs.create({ url: cmd.url || 'about:blank', windowId: (ref && ref.windowId) || undefined, active: false });+      await pupExtWaitComplete(t.id, cmd.waitMs || 30000);+      const t2 = await chrome.tabs.get(t.id);+      out = { id: cmd.id, ok: true, tabId: t.id, windowId: t2.windowId, url: t2.url, title: t2.title };+    } else if (cmd.action === 'activate') {+      const t = await pupExtTab(cmd);+      await chrome.tabs.update(t.id, { active: true });+      out = { id: cmd.id, ok: true, tabId: t.id, windowId: t.windowId };+    } else if (cmd.action === 'screenshot') {+      const t = await pupExtTab(cmd);+      await chrome.tabs.update(t.id, { active: true });+      await new Promise((r) => setTimeout(r, 350));+      const dataUrl = await chrome.tabs.captureVisibleTab(t.windowId, { format: 'png' });+      out = { id: cmd.id, ok: true, tabId: t.id, dataUrl: dataUrl };+    } else if (cmd.action === 'tabs') {+      const tabs = await chrome.tabs.query({});+      out = { id: cmd.id, ok: true, tabs: tabs.map((t) => ({ tabId: t.id, url: t.url, title: t.title, active: !!t.active, windowId: t.windowId })) };+    } else if (cmd.action === 'close') {+      if (cmd.tabId) await chrome.tabs.remove(cmd.tabId);+      else if (cmd.windowId) await chrome.windows.remove(cmd.windowId);+      out = { id: cmd.id, ok: true };+    } else if (cmd.action === 'closeAll') {+      const wins = await chrome.windows.getAll({});+      for (const w of wins) { try { await chrome.windows.remove(w.id); } catch (e) {} }+      out = { id: cmd.id, ok: true, closed: wins.length };+    } else if (cmd.action === 'fit') {+      // Size the lane's window to the primary display's WORK AREA, normal state.+      // Deliberately NOT chrome.windows.update({state:'maximized'}) and NOT ab's+      // desktop_set_window_bounds: pup-bridge-dev's geometry rule bans both because each one RAISES+      // the window over the user's work. Plain left/top/width/height leaves the z-order alone. Both+      // sides of this call are LOGICAL px (system.display workArea and windows.update agree), which+      // is why the DPI mismatch that bites the ab path cannot happen here.+      const wins = await chrome.windows.getAll({});+      const displays = await chrome.system.display.getInfo();+      const d = displays.find((x) => x.isPrimary) || displays[0];+      const wa = d && d.workArea;+      let fitted = 0;+      if (wa) {+        for (const w of wins) {+          if (w.type !== 'normal') continue;+          try {+            await chrome.windows.update(w.id, { state: 'normal', left: wa.left, top: wa.top, width: wa.width, height: wa.height });+            fitted++;+          } catch (e) {}+        }+      }+      out = { id: cmd.id, ok: true, fitted: fitted, workArea: wa || null };+    } else if (cmd.action === 'ping') {+      out = { id: cmd.id, ok: true, pong: true, ua: navigator.userAgent };+    }+  } catch (e) { out = { id: cmd.id, ok: false, error: String(e && e.message ? e.message : e) }; }+  try {+    await fetch(`http://127.0.0.1:${found.port}/extresult`, {+      method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify(out),+    });+  } catch (e) {}+  pupExtBusy = false;+}++// A tight poll keeps the MV3 worker alive while pup is driving; the alarm revives it if it ever+// does get torn down between commands.+setInterval(pupExtPoll, 1200);+chrome.alarms.create('pup-extdrive', { periodInMinutes: 0.5 });+chrome.alarms.onAlarm.addListener((a) => { if (a.name === 'pup-extdrive') pupExtPoll(); });+pupExtPoll();