A browser extension that lets you summarize any webpage and ask questions using AI.
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

firefox support + bug fixes

+326 -23
+86
BROWSER_SUPPORT.md
··· 1 + # Browser Support 2 + 3 + This extension supports both **Chrome** and **Firefox** with separate manifest files. 4 + 5 + ## Building for Different Browsers 6 + 7 + ### Chrome 8 + ```bash 9 + ./build.sh chrome 10 + ``` 11 + Or manually: 12 + ```bash 13 + cp manifest-chrome.json manifest.json 14 + ``` 15 + 16 + ### Firefox 17 + ```bash 18 + ./build.sh firefox 19 + ``` 20 + Or manually: 21 + ```bash 22 + cp manifest-firefox.json manifest.json 23 + ``` 24 + 25 + ## Key Differences 26 + 27 + | Feature | Chrome | Firefox | 28 + |---------|--------|---------| 29 + | Background | `service_worker` | `background.scripts` + `persistent: false` | 30 + | UI Opening | Toolbar popup (action) | Popup window (450x650) | 31 + | Keyboard shortcut | `Cmd+Shift+U` opens popup + triggers summarize | `Cmd+Shift+U` opens popup window + triggers summarize | 32 + | Context menu | Opens popup + triggers summarize | Opens popup window + triggers summarize | 33 + | Browser detection | Uses `chrome.*` APIs | Uses `browser.*` APIs | 34 + 35 + ## Loading the Extension 36 + 37 + ### Chrome 38 + 1. Go to `chrome://extensions/` 39 + 2. Enable "Developer mode" 40 + 3. Click "Load unpacked" 41 + 4. Select the extension folder (with `manifest.json` configured for Chrome) 42 + 43 + ### Firefox 44 + 1. Go to `about:debugging#/runtime/this-firefox` 45 + 2. Click "Load Temporary Add-on" 46 + 3. Select `manifest.json` (configured for Firefox) or `manifest-firefox.json` 47 + 48 + **Note:** Firefox requires version 109.0 or higher. 49 + 50 + ## Development 51 + 52 + The code automatically detects the browser and uses the appropriate APIs: 53 + 54 + ### background.js 55 + - Sets `triggerSummarize: true` and `targetTabId` when keyboard shortcut or context menu is triggered 56 + - **Chrome**: Calls `chrome.action.openPopup()` to open the toolbar popup 57 + - **Firefox**: Creates a popup window (450x650 pixels) 58 + 59 + ### popup.js 60 + - On load, checks `targetTabId` from storage 61 + - **Firefox**: Uses `targetTabId` to get tab info (popup is in separate window) 62 + - **Chrome**: Queries active tab in current window 63 + - Checks `triggerSummarize` flag to auto-start summarization 64 + - Clears both flags after use 65 + 66 + ## Keyboard Shortcut 67 + 68 + Both browsers use **Cmd+Shift+U** (Mac) or **Ctrl+Shift+U** (Windows/Linux). 69 + 70 + ### How it Works 71 + 72 + 1. User presses keyboard shortcut / right-clicks → "Summarize this page" 73 + 2. `background.js` receives the command 74 + 3. Stores `triggerSummarize: true` and `targetTabId` in session storage 75 + 4. **Chrome**: Opens popup via `action.openPopup()` 76 + 5. **Firefox**: Creates popup window via `windows.create()` 77 + 6. `popup.js` reads `targetTabId` and `triggerSummarize` 78 + 7. Auto-triggers summarization 79 + 8. Clears the flags 80 + 81 + ## Files Created 82 + 83 + - `manifest.json` - Main manifest (build target) 84 + - `manifest-chrome.json` - Chrome-specific manifest 85 + - `manifest-firefox.json` - Firefox-specific manifest 86 + - `build.sh` - Build script to switch between browsers
+21
build.sh
··· 1 + #!/bin/bash 2 + 3 + # Build script for WebAI Summarizer 4 + # Usage: ./build.sh [chrome|firefox] 5 + 6 + BROWSER=${1:-chrome} 7 + 8 + if [ "$BROWSER" = "chrome" ]; then 9 + echo "Building for Chrome..." 10 + cp manifest-chrome.json manifest.json 11 + echo "✓ manifest.json updated for Chrome (uses service_worker)" 12 + elif [ "$BROWSER" = "firefox" ]; then 13 + echo "Building for Firefox..." 14 + cp manifest-firefox.json manifest.json 15 + echo "✓ manifest.json updated for Firefox (uses background.scripts + sidebar_action)" 16 + else 17 + echo "Usage: ./build.sh [chrome|firefox]" 18 + exit 1 19 + fi 20 + 21 + echo "Ready to load extension!"
+62
manifest-chrome.json
··· 1 + { 2 + "manifest_version": 3, 3 + "name": "WebAI Summarizer", 4 + "version": "1.0.0", 5 + "description": "Ask questions and summarize any webpage with AI", 6 + "permissions": [ 7 + "activeTab", 8 + "tabs", 9 + "storage", 10 + "scripting", 11 + "declarativeNetRequest", 12 + "contextMenus" 13 + ], 14 + "commands": { 15 + "summarize-page": { 16 + "suggested_key": { 17 + "default": "Ctrl+Shift+U", 18 + "mac": "Command+Shift+U" 19 + }, 20 + "description": "Summarize the current page" 21 + } 22 + }, 23 + "host_permissions": [ 24 + "http://localhost/*", 25 + "http://*/*", 26 + "https://*/*" 27 + ], 28 + "declarative_net_request": { 29 + "rule_resources": [ 30 + { 31 + "id": "rewrite_origin", 32 + "enabled": true, 33 + "path": "rules.json" 34 + } 35 + ] 36 + }, 37 + "action": { 38 + "default_popup": "popup/popup.html", 39 + "default_icon": { 40 + "16": "icons/icon16.png", 41 + "32": "icons/icon32.png", 42 + "48": "icons/icon48.png", 43 + "128": "icons/icon128.png" 44 + } 45 + }, 46 + "background": { 47 + "service_worker": "scripts/background.js" 48 + }, 49 + "content_scripts": [ 50 + { 51 + "matches": ["<all_urls>"], 52 + "js": ["scripts/content.js"] 53 + } 54 + ], 55 + "options_page": "options/options.html", 56 + "icons": { 57 + "16": "icons/icon16.png", 58 + "32": "icons/icon32.png", 59 + "48": "icons/icon48.png", 60 + "128": "icons/icon128.png" 61 + } 62 + }
+68
manifest-firefox.json
··· 1 + { 2 + "manifest_version": 3, 3 + "name": "WebAI Summarizer", 4 + "version": "1.0.0", 5 + "description": "Ask questions and summarize any webpage with AI", 6 + "browser_specific_settings": { 7 + "gecko": { 8 + "id": "webai-summarizer@anomaly.co", 9 + "strict_min_version": "109.0" 10 + } 11 + }, 12 + "permissions": [ 13 + "activeTab", 14 + "tabs", 15 + "storage", 16 + "scripting", 17 + "declarativeNetRequest", 18 + "contextMenus" 19 + ], 20 + "commands": { 21 + "summarize-page": { 22 + "suggested_key": { 23 + "default": "Ctrl+Shift+U", 24 + "mac": "Command+Shift+U" 25 + }, 26 + "description": "Summarize the current page" 27 + } 28 + }, 29 + "host_permissions": [ 30 + "http://localhost/*", 31 + "http://*/*", 32 + "https://*/*" 33 + ], 34 + "declarative_net_request": { 35 + "rule_resources": [ 36 + { 37 + "id": "rewrite_origin", 38 + "enabled": true, 39 + "path": "rules.json" 40 + } 41 + ] 42 + }, 43 + "action": { 44 + "default_popup": "popup/popup.html", 45 + "default_icon": { 46 + "16": "icons/icon16.png", 47 + "32": "icons/icon32.png", 48 + "48": "icons/icon48.png", 49 + "128": "icons/icon128.png" 50 + } 51 + }, 52 + "background": { 53 + "scripts": ["scripts/background.js"] 54 + }, 55 + "content_scripts": [ 56 + { 57 + "matches": ["<all_urls>"], 58 + "js": ["scripts/content.js"] 59 + } 60 + ], 61 + "options_page": "options/options.html", 62 + "icons": { 63 + "16": "icons/icon16.png", 64 + "32": "icons/icon32.png", 65 + "48": "icons/icon48.png", 66 + "128": "icons/icon128.png" 67 + } 68 + }
+2 -2
manifest.json
··· 14 14 "commands": { 15 15 "summarize-page": { 16 16 "suggested_key": { 17 - "default": "Ctrl+Shift+S", 18 - "mac": "Command+Shift+S" 17 + "default": "Ctrl+Shift+U", 18 + "mac": "Command+Shift+U" 19 19 }, 20 20 "description": "Summarize the current page" 21 21 }
+3 -2
options/options.html
··· 75 75 <div class="form-group"> 76 76 <label>Keyboard Shortcut</label> 77 77 <div class="shortcut-display"> 78 - <code>Ctrl+Shift+S</code> (Windows/Linux) &nbsp;·&nbsp; <code>Cmd+Shift+S</code> (Mac) 78 + <code>Ctrl+Shift+U</code> (Windows/Linux) &nbsp;·&nbsp; <code>Cmd+Shift+U</code> (Mac) 79 79 </div> 80 80 <p class="help"> 81 - <a href="#" id="keyboard-shortcuts-link">Change keyboard shortcut</a> 81 + <a href="#" id="keyboard-shortcuts-link">Change or enable keyboard shortcut</a><br> 82 + <small style="color: var(--text-muted);">Chrome: You may need to manually enable the shortcut after install.</small> 82 83 </p> 83 84 </div> 84 85
+18 -1
options/options.js
··· 119 119 const keyboardShortcutsLink = document.getElementById("keyboard-shortcuts-link"); 120 120 keyboardShortcutsLink.addEventListener("click", (e) => { 121 121 e.preventDefault(); 122 - chrome.tabs.create({ url: "chrome://extensions/shortcuts" }); 122 + // Detect Firefox vs Chrome for keyboard shortcuts URL 123 + if (typeof browser !== 'undefined') { 124 + // Firefox: Can't open about:addons programmatically, show instructions 125 + const infoDiv = document.getElementById("shortcuts-info") || createShortcutsInfo(); 126 + infoDiv.style.display = 'block'; 127 + } else { 128 + // Chrome/Edge: Open built-in shortcuts page 129 + chrome.tabs.create({ url: "chrome://extensions/shortcuts" }); 130 + } 123 131 }); 132 + 133 + function createShortcutsInfo() { 134 + const infoDiv = document.createElement("div"); 135 + infoDiv.id = "shortcuts-info"; 136 + infoDiv.style.cssText = "margin-top:10px;padding:10px 14px;background:#faf8f4;border:1px solid #e0d8cc;border-radius:6px;font-size:12.5px;line-height:1.5;"; 137 + infoDiv.innerHTML = 'ℹ️ To manage shortcuts in Firefox:<br>1. Type <code style="background:#ede8de;padding:1px 5px;border-radius:3px;font-size:11px;">about:addons</code> in the address bar<br>2. Click the gear icon (⚙️) → <strong>Manage Extension Shortcuts</strong>'; 138 + keyboardShortcutsLink.parentNode.appendChild(infoDiv); 139 + return infoDiv; 140 + } 124 141 125 142 // Load settings 126 143 async function loadSettings() {
+49 -15
popup/popup.js
··· 97 97 currentTheme = (await chrome.storage.sync.get("theme")).theme || "system"; 98 98 applyTheme(currentTheme); 99 99 100 + // Check if we have a target tab from background script (for Firefox popup window) 101 + // Also check for triggerSummarize flag - read once and clear immediately to prevent stale state 102 + const sessionData = await chrome.storage.session.get(["targetTabId", "triggerSummarize"]); 103 + const targetTabId = sessionData.targetTabId; 104 + const shouldAutoSummarize = sessionData.triggerSummarize; 105 + 106 + // Clear flags immediately to prevent them from persisting to next popup open 107 + if (targetTabId || shouldAutoSummarize) { 108 + await chrome.storage.session.remove(["targetTabId", "triggerSummarize"]); 109 + } 110 + 100 111 // Get current tab info 101 - const [tab] = await chrome.tabs.query({ active: true, currentWindow: true }); 102 - currentTabId = tab.id; 103 - currentTabUrl = tab.url; 112 + // For Firefox popup window, we need to use targetTabId from storage 113 + // For Chrome popup, we query the active tab in current window 114 + let tab; 115 + if (targetTabId && typeof browser !== 'undefined') { 116 + // Firefox: Use the stored target tab ID 117 + try { 118 + tab = await chrome.tabs.get(targetTabId); 119 + } catch (e) { 120 + // Tab might have been closed, fall back to active tab 121 + const tabs = await chrome.tabs.query({ active: true, currentWindow: true }); 122 + tab = tabs[0]; 123 + } 124 + } else { 125 + // Chrome: Query active tab in current window 126 + const tabs = await chrome.tabs.query({ active: true, currentWindow: true }); 127 + tab = tabs[0]; 128 + } 129 + 130 + currentTabId = tab?.id; 131 + currentTabUrl = tab?.url; 104 132 105 133 // Check for cached summary for this tab 106 134 const cached = await chrome.storage.session.get([ ··· 163 191 // No summaries yet 164 192 setSummarizeLabel("Quick Summary"); 165 193 } 166 - } else { 194 + } else { 167 195 // No cache, extract fresh content 168 196 await extractPageContent(); 169 197 isExtracting = false; 170 198 summarizeBtn.disabled = false; 171 199 setSummarizeLabel("Quick Summary"); 172 200 173 - // Check if we should auto-trigger summarize (from context menu) 174 - const session = await chrome.storage.session.get(["triggerSummarize"]); 175 - if (session.triggerSummarize) { 176 - // Clear the flag 177 - await chrome.storage.session.remove("triggerSummarize"); 201 + // Check if we should auto-trigger summarize (from keyboard shortcut or context menu) 202 + if (shouldAutoSummarize) { 178 203 // Trigger quick summarize 179 204 if (!isLoading && !isExtracting && currentPageContent) { 180 205 await generateQuickSummary(); 181 206 } 182 207 } 183 208 } 184 - }); 209 + }); 185 210 186 211 function resetUI() { 187 212 quickSummary = ""; ··· 229 254 230 255 async function extractPageContent() { 231 256 try { 232 - const [tab] = await chrome.tabs.query({ 233 - active: true, 234 - currentWindow: true, 235 - }); 257 + // Use currentTabId which was determined at popup load 258 + // This works for both Chrome popup and Firefox popup window 259 + if (!currentTabId) { 260 + currentPageContent = ""; 261 + contentWasTruncated = false; 262 + return; 263 + } 264 + 265 + const tab = await chrome.tabs.get(currentTabId); 266 + 236 267 if ( 237 268 !tab.url || 238 269 tab.url.startsWith("chrome://") || 239 270 tab.url.startsWith("chrome-extension://") || 240 - tab.url.startsWith("edge://") 271 + tab.url.startsWith("edge://") || 272 + tab.url.startsWith("about:") || 273 + tab.url.startsWith("moz-extension://") || 274 + tab.url.startsWith("resource://") 241 275 ) { 242 276 currentPageContent = ""; 243 277 contentWasTruncated = false;
+17 -3
scripts/background.js
··· 66 66 }); 67 67 68 68 // Handle keyboard shortcut 69 + // Chrome & Firefox: Listen for "summarize-page" command 69 70 chrome.commands.onCommand.addListener((command) => { 70 - if (command === "summarize-page") { 71 + if (command === "summarize-page" || command === "open-summarizer") { 71 72 chrome.tabs.query({ active: true, currentWindow: true }).then((tabs) => { 72 73 if (tabs[0]) { 73 74 triggerSummarizeForTab(tabs[0].id); ··· 80 81 // Store a flag to trigger summarize when popup opens 81 82 chrome.storage.session.set({ triggerSummarize: true, targetTabId: tabId }); 82 83 83 - // Open the popup 84 - chrome.action.openPopup(); 84 + // Firefox: Create a popup window 85 + // Chrome: Use action.openPopup() for toolbar popup 86 + if (typeof browser !== 'undefined') { 87 + // Firefox: Create a popup window matching the UI size (extra height for browser chrome) 88 + chrome.windows.create({ 89 + url: chrome.runtime.getURL('popup/popup.html'), 90 + type: 'popup', 91 + width: 400, 92 + height: 600, 93 + focused: true 94 + }); 95 + } else { 96 + // Chrome: Programmatically open the popup 97 + chrome.action.openPopup(); 98 + } 85 99 } 86 100 87 101 chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {