From a989f0dcf1f6859135dfc5f697add6bddd14face Mon Sep 17 00:00:00 2001 From: alexveley Date: Tue, 22 Sep 2026 08:29:33 -0400 Subject: [PATCH] Declare Firefox data-collection consent and stop assigning innerHTML in options. --- PRIVACY.md | 2 +- extension/manifest.json | 7 +++++-- extension/options.js | 35 ++++++++++++++++++++--------------- 3 files changed, 26 insertions(+), 18 deletions(-) diff --git a/PRIVACY.md b/PRIVACY.md index 3f9474d..1a2b30d 100644 --- a/PRIVACY.md +++ b/PRIVACY.md @@ -4,7 +4,7 @@ Bookmarks API for Scripting and AI runs on your computer. It does not send your ## What it can see -With the `bookmarks` permission, the add-on can read and change bookmarks in the Firefox profile where it is installed. That is the product. Local scripts you authorize can request the same operations through the loopback API. +With the `bookmarks` permission, the add-on can read and change bookmarks in the Firefox profile where it is installed. That is the product. Local scripts you authorize can request the same operations through the loopback API. Firefox’s install prompt lists this as bookmark information (`bookmarksInfo`). Nothing is sent to the authors. ## What it stores diff --git a/extension/manifest.json b/extension/manifest.json index 7eeba38..6109e9b 100644 --- a/extension/manifest.json +++ b/extension/manifest.json @@ -2,12 +2,15 @@ "manifest_version": 3, "name": "Bookmarks API for Scripting and AI", "short_name": "Bookmarks API", - "version": "0.4.2", + "version": "0.4.3", "description": "An API add-on for Firefox to allow agentic and script-based management of user bookmarks.", "browser_specific_settings": { "gecko": { "id": "firefox-agent-bridge@easygoingaming.com", - "strict_min_version": "115.0" + "strict_min_version": "115.0", + "data_collection_permissions": { + "required": ["bookmarksInfo"] + } } }, "permissions": ["bookmarks", "nativeMessaging", "storage"], diff --git a/extension/options.js b/extension/options.js index 86a0b1b..971798b 100644 --- a/extension/options.js +++ b/extension/options.js @@ -31,30 +31,35 @@ function showError(text) { } function render(clients) { + rows.replaceChildren(); if (!clients.length) { - rows.innerHTML = 'No clients yet. Generate a secret to get started.'; + const tr = document.createElement("tr"); + const td = document.createElement("td"); + td.colSpan = 4; + td.className = "muted"; + td.textContent = "No clients yet. Generate a secret to get started."; + tr.appendChild(td); + rows.appendChild(tr); return; } - rows.innerHTML = ""; for (const client of clients) { const tr = document.createElement("tr"); - tr.innerHTML = ` - ${escapeHtml(client.name)} - ${escapeHtml(client.created || "")} - ${escapeHtml(client.last_used || "never")} - `; + for (const text of [client.name, client.created || "", client.last_used || "never"]) { + const td = document.createElement("td"); + td.textContent = text; + tr.appendChild(td); + } + const action = document.createElement("td"); + const btn = document.createElement("button"); + btn.type = "button"; + btn.textContent = "Revoke"; + btn.dataset.revoke = client.id; + action.appendChild(btn); + tr.appendChild(action); rows.appendChild(tr); } } -function escapeHtml(value) { - return String(value) - .replace(/&/g, "&") - .replace(//g, ">") - .replace(/"/g, """); -} - async function refresh() { render(await listClients()); try {