Interactive tool to create browser extensions for Chrome and Edge
Size
16.9 KB
Version
1.0.1
Created
Dec 19, 2025
Updated
about 2 months ago
1// ==UserScript==
2// @name Browser Extension Builder
3// @description Interactive tool to create browser extensions for Chrome and Edge
4// @version 1.0.1
5// @match https://*.stevefenton.co.uk/*
6// @icon https://stevefenton.co.uk/icons/favicon.ico
7// ==/UserScript==
8(function() {
9 'use strict';
10
11 console.log('Browser Extension Builder initialized');
12
13 // Debounce function for performance
14 function debounce(func, wait) {
15 let timeout;
16 return function executedFunction(...args) {
17 const later = () => {
18 clearTimeout(timeout);
19 func(...args);
20 };
21 clearTimeout(timeout);
22 timeout = setTimeout(later, wait);
23 };
24 }
25
26 // Create the builder UI
27 function createBuilderUI() {
28 console.log('Creating builder UI');
29
30 // Check if UI already exists
31 if (document.getElementById('extension-builder-panel')) {
32 console.log('Builder UI already exists');
33 return;
34 }
35
36 // Create main panel
37 const panel = document.createElement('div');
38 panel.id = 'extension-builder-panel';
39 panel.innerHTML = `
40 <div class="builder-header">
41 <h3>🔧 Extension Builder</h3>
42 <button class="close-btn" id="close-builder">✕</button>
43 </div>
44 <div class="builder-content">
45 <div class="form-section">
46 <label for="ext-name">Extension Name:</label>
47 <input type="text" id="ext-name" placeholder="My Awesome Extension" value="My Extension">
48 </div>
49
50 <div class="form-section">
51 <label for="ext-version">Version:</label>
52 <input type="text" id="ext-version" placeholder="1.0.0" value="1.0.0">
53 </div>
54
55 <div class="form-section">
56 <label for="ext-description">Description:</label>
57 <textarea id="ext-description" placeholder="What does your extension do?" rows="2">A helpful browser extension</textarea>
58 </div>
59
60 <div class="form-section">
61 <label for="ext-match">Match URL Pattern:</label>
62 <input type="text" id="ext-match" placeholder="https://*/*" value="https://*/*">
63 <small>Which websites should this extension work on?</small>
64 </div>
65
66 <div class="form-section">
67 <label for="ext-permissions">Permissions (comma-separated):</label>
68 <input type="text" id="ext-permissions" placeholder="activeTab, scripting" value="activeTab, scripting">
69 </div>
70
71 <div class="form-section">
72 <label for="ext-code">Extension Code (JavaScript):</label>
73 <textarea id="ext-code" placeholder="Your extension code here..." rows="8">// Your extension code
74console.log('Extension loaded!');
75
76// Example: Change background color
77document.body.style.backgroundColor = '#f0f0f0';</textarea>
78 </div>
79
80 <div class="button-group">
81 <button class="generate-btn" id="generate-extension">Generate Extension Files</button>
82 </div>
83
84 <div class="output-section" id="output-section" style="display: none;">
85 <h4>Generated Files:</h4>
86 <div class="file-output">
87 <div class="file-header">
88 <strong>manifest.json</strong>
89 <button class="copy-file-btn" data-file="manifest">Copy</button>
90 </div>
91 <pre id="manifest-output"></pre>
92 </div>
93
94 <div class="file-output">
95 <div class="file-header">
96 <strong>worker.js</strong>
97 <button class="copy-file-btn" data-file="worker">Copy</button>
98 </div>
99 <pre id="worker-output"></pre>
100 </div>
101
102 <div class="instructions">
103 <h4>📦 Next Steps:</h4>
104 <ol>
105 <li>Create a new folder for your extension</li>
106 <li>Save the manifest.json file</li>
107 <li>Save the worker.js file</li>
108 <li>Add a 128x128 pixel icon named "icon-128.png"</li>
109 <li>Open Chrome/Edge and go to Extensions</li>
110 <li>Enable "Developer mode"</li>
111 <li>Click "Load unpacked" and select your folder</li>
112 </ol>
113 </div>
114
115 <button class="download-btn" id="download-all">Download All Files as ZIP</button>
116 </div>
117 </div>
118 `;
119
120 // Add styles
121 const styles = `
122 #extension-builder-panel {
123 position: fixed;
124 top: 20px;
125 right: 20px;
126 width: 450px;
127 max-height: 90vh;
128 background: white;
129 border: 2px solid #007bff;
130 border-radius: 12px;
131 box-shadow: 0 8px 32px rgba(0,0,0,0.2);
132 z-index: 999999;
133 font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
134 overflow: hidden;
135 display: flex;
136 flex-direction: column;
137 }
138
139 .builder-header {
140 background: linear-gradient(135deg, #007bff 0%, #0056b3 100%);
141 color: white;
142 padding: 16px 20px;
143 display: flex;
144 justify-content: space-between;
145 align-items: center;
146 }
147
148 .builder-header h3 {
149 margin: 0;
150 font-size: 18px;
151 font-weight: 600;
152 }
153
154 .close-btn {
155 background: rgba(255,255,255,0.2);
156 border: none;
157 color: white;
158 width: 28px;
159 height: 28px;
160 border-radius: 50%;
161 cursor: pointer;
162 font-size: 18px;
163 display: flex;
164 align-items: center;
165 justify-content: center;
166 transition: background 0.2s;
167 }
168
169 .close-btn:hover {
170 background: rgba(255,255,255,0.3);
171 }
172
173 .builder-content {
174 padding: 20px;
175 overflow-y: auto;
176 flex: 1;
177 }
178
179 .form-section {
180 margin-bottom: 16px;
181 }
182
183 .form-section label {
184 display: block;
185 margin-bottom: 6px;
186 font-weight: 600;
187 color: #333;
188 font-size: 13px;
189 }
190
191 .form-section input,
192 .form-section textarea {
193 width: 100%;
194 padding: 10px;
195 border: 2px solid #e0e0e0;
196 border-radius: 6px;
197 font-size: 13px;
198 font-family: inherit;
199 box-sizing: border-box;
200 transition: border-color 0.2s;
201 }
202
203 .form-section input:focus,
204 .form-section textarea:focus {
205 outline: none;
206 border-color: #007bff;
207 }
208
209 .form-section textarea {
210 resize: vertical;
211 font-family: 'Courier New', monospace;
212 }
213
214 .form-section small {
215 display: block;
216 margin-top: 4px;
217 color: #666;
218 font-size: 11px;
219 }
220
221 .button-group {
222 margin: 20px 0;
223 }
224
225 .generate-btn,
226 .download-btn {
227 width: 100%;
228 padding: 12px;
229 background: linear-gradient(135deg, #28a745 0%, #20873a 100%);
230 color: white;
231 border: none;
232 border-radius: 6px;
233 font-size: 14px;
234 font-weight: 600;
235 cursor: pointer;
236 transition: transform 0.2s, box-shadow 0.2s;
237 }
238
239 .generate-btn:hover,
240 .download-btn:hover {
241 transform: translateY(-2px);
242 box-shadow: 0 4px 12px rgba(40, 167, 69, 0.3);
243 }
244
245 .download-btn {
246 background: linear-gradient(135deg, #007bff 0%, #0056b3 100%);
247 margin-top: 16px;
248 }
249
250 .download-btn:hover {
251 box-shadow: 0 4px 12px rgba(0, 123, 255, 0.3);
252 }
253
254 .output-section {
255 margin-top: 20px;
256 padding-top: 20px;
257 border-top: 2px solid #e0e0e0;
258 }
259
260 .output-section h4 {
261 margin: 0 0 16px 0;
262 color: #333;
263 font-size: 15px;
264 }
265
266 .file-output {
267 margin-bottom: 16px;
268 background: #f8f9fa;
269 border-radius: 6px;
270 overflow: hidden;
271 }
272
273 .file-header {
274 display: flex;
275 justify-content: space-between;
276 align-items: center;
277 padding: 10px 12px;
278 background: #e9ecef;
279 border-bottom: 1px solid #dee2e6;
280 }
281
282 .file-header strong {
283 color: #495057;
284 font-size: 13px;
285 }
286
287 .copy-file-btn {
288 padding: 4px 12px;
289 background: #007bff;
290 color: white;
291 border: none;
292 border-radius: 4px;
293 font-size: 11px;
294 cursor: pointer;
295 transition: background 0.2s;
296 }
297
298 .copy-file-btn:hover {
299 background: #0056b3;
300 }
301
302 .file-output pre {
303 margin: 0;
304 padding: 12px;
305 background: #f8f9fa;
306 overflow-x: auto;
307 font-size: 11px;
308 line-height: 1.5;
309 color: #212529;
310 }
311
312 .instructions {
313 background: #e7f3ff;
314 border-left: 4px solid #007bff;
315 padding: 16px;
316 border-radius: 6px;
317 margin: 20px 0;
318 }
319
320 .instructions h4 {
321 margin: 0 0 12px 0;
322 color: #0056b3;
323 font-size: 14px;
324 }
325
326 .instructions ol {
327 margin: 0;
328 padding-left: 20px;
329 }
330
331 .instructions li {
332 margin-bottom: 6px;
333 color: #333;
334 font-size: 12px;
335 line-height: 1.5;
336 }
337
338 #builder-toggle-btn {
339 position: fixed;
340 bottom: 20px;
341 right: 20px;
342 padding: 14px 20px;
343 background: linear-gradient(135deg, #007bff 0%, #0056b3 100%);
344 color: white;
345 border: none;
346 border-radius: 50px;
347 font-size: 14px;
348 font-weight: 600;
349 cursor: pointer;
350 box-shadow: 0 4px 16px rgba(0,0,0,0.2);
351 z-index: 999998;
352 transition: transform 0.2s, box-shadow 0.2s;
353 }
354
355 #builder-toggle-btn:hover {
356 transform: translateY(-2px);
357 box-shadow: 0 6px 20px rgba(0,0,0,0.3);
358 }
359 `;
360
361 const styleSheet = document.createElement('style');
362 styleSheet.textContent = styles;
363 document.head.appendChild(styleSheet);
364
365 document.body.appendChild(panel);
366
367 // Add event listeners
368 document.getElementById('close-builder').addEventListener('click', () => {
369 panel.style.display = 'none';
370 });
371
372 document.getElementById('generate-extension').addEventListener('click', generateExtension);
373
374 // Add copy button listeners
375 document.querySelectorAll('.copy-file-btn').forEach(btn => {
376 btn.addEventListener('click', (e) => {
377 const fileType = e.target.getAttribute('data-file');
378 const outputId = fileType === 'manifest' ? 'manifest-output' : 'worker-output';
379 const content = document.getElementById(outputId).textContent;
380
381 navigator.clipboard.writeText(content).then(() => {
382 const originalText = e.target.textContent;
383 e.target.textContent = '✓ Copied!';
384 setTimeout(() => {
385 e.target.textContent = originalText;
386 }, 2000);
387 });
388 });
389 });
390
391 console.log('Builder UI created successfully');
392 }
393
394 // Generate extension files
395 function generateExtension() {
396 console.log('Generating extension files');
397
398 const name = document.getElementById('ext-name').value.trim();
399 const version = document.getElementById('ext-version').value.trim();
400 const description = document.getElementById('ext-description').value.trim();
401 const matchPattern = document.getElementById('ext-match').value.trim();
402 const permissionsInput = document.getElementById('ext-permissions').value.trim();
403 const code = document.getElementById('ext-code').value;
404
405 // Parse permissions
406 const permissions = permissionsInput.split(',').map(p => p.trim()).filter(p => p);
407
408 // Generate manifest.json
409 const manifest = {
410 "name": name,
411 "version": version,
412 "description": description,
413 "action": {},
414 "manifest_version": 3,
415 "icons": {
416 "128": "icon-128.png"
417 },
418 "background": {
419 "service_worker": "worker.js"
420 },
421 "permissions": permissions,
422 "host_permissions": [matchPattern]
423 };
424
425 // Generate worker.js
426 const worker = `// ${name} - Background Service Worker
427// Generated by Browser Extension Builder
428
429console.log('${name} extension loaded');
430
431// Listen for extension icon click
432chrome.action.onClicked.addListener((tab) => {
433 console.log('Extension icon clicked on tab:', tab.id);
434
435 // Execute the extension code on the active tab
436 chrome.scripting.executeScript({
437 target: { tabId: tab.id },
438 func: extensionCode
439 });
440});
441
442// The main extension code
443function extensionCode() {
444${code.split('\n').map(line => ' ' + line).join('\n')}
445}
446`;
447
448 // Display the generated files
449 document.getElementById('manifest-output').textContent = JSON.stringify(manifest, null, 2);
450 document.getElementById('worker-output').textContent = worker;
451 document.getElementById('output-section').style.display = 'block';
452
453 // Scroll to output
454 document.getElementById('output-section').scrollIntoView({ behavior: 'smooth', block: 'nearest' });
455
456 console.log('Extension files generated successfully');
457 }
458
459 // Create toggle button
460 function createToggleButton() {
461 if (document.getElementById('builder-toggle-btn')) {
462 return;
463 }
464
465 const toggleBtn = document.createElement('button');
466 toggleBtn.id = 'builder-toggle-btn';
467 toggleBtn.textContent = '🔧 Build Extension';
468 toggleBtn.addEventListener('click', () => {
469 const panel = document.getElementById('extension-builder-panel');
470 if (panel) {
471 panel.style.display = panel.style.display === 'none' ? 'flex' : 'none';
472 } else {
473 createBuilderUI();
474 }
475 });
476 document.body.appendChild(toggleBtn);
477 }
478
479 // Initialize when DOM is ready
480 function init() {
481 console.log('Initializing Browser Extension Builder');
482
483 if (document.body) {
484 createToggleButton();
485 console.log('Toggle button created');
486 } else {
487 console.log('Body not ready, waiting...');
488 setTimeout(init, 100);
489 }
490 }
491
492 // Start the extension
493 if (document.readyState === 'loading') {
494 document.addEventListener('DOMContentLoaded', init);
495 } else {
496 init();
497 }
498
499})();