Skip to content

Simple Counter Extension

What it does

  • Counts button clicks on any webpage
  • Shows count in popup
  • Saves count permanently
  • Settings page to change highlight color and reset counter
  • Shows badge number on icon

Full flow traced

  1. User clicks button on webpage
  2. content.js detects it → highlights button
  3. content.js sends "incrementCounter" to background.js
  4. background.js gets count from storage → adds 1 → saves back
  5. background.js updates icon badge
  6. User clicks extension icon → popup opens
  7. popup.js asks background for count
  8. Shows in popup UI
  9. User opens settings → changes color → saves
  10. background.js detects storage change → notifies all content scripts
  11. Next button click uses new color

Extension Overview

What it does: - Counts how many times you click a button on web pages - Shows count in popup - Saves count so it persists - Has settings to reset counter or change button color - Highlights clicked buttons on page

File Structure

extension/
├── manifest.json
├── background.js
├── content.js
├── popup.html
├── popup.js
├── options.html
├── options.js
└── icon.png (16x16, 48x48, 128x128)

manifest.json

Purpose: Tells Chrome what this extension is and what it can do

{
  "manifest_version": 3,
  "name": "Click Counter",
  "version": "1.0.0",
  "description": "Counts button clicks on web pages",

  "icons": {
    "16": "icon16.png",
    "48": "icon48.png",
    "128": "icon128.png"
  },

  "action": {
    "default_popup": "popup.html",
    "default_icon": {
      "16": "icon16.png",
      "32": "icon32.png"
    },
    "default_title": "Click Counter"
  },

  "background": {
    "service_worker": "background.js"
  },

  "content_scripts": [{
    "matches": ["https://*/*", "http://*/*"],
    "js": ["content.js"],
    "run_at": "document_idle"
  }],

  "permissions": [
    "storage",
    "activeTab"
  ],

  "options_page": "options.html"
}

background.js

Purpose: Runs in background, handles events, coordinates everything. This is the "brain" - has full Chrome API access but no DOM access.

// This runs when extension is installed or updated
chrome.runtime.onInstalled.addListener((details) => {
  console.log('Extension installed/updated');

  if (details.reason === 'install') {
    // First install - set default values
    chrome.storage.local.set({
      clickCount: 0,
      highlightColor: '#ffff00'
    });
    console.log('Default values set');
  }
});

// Listen for messages from content script or popup
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
  console.log('Background received message:', message);

  // Handle different message types
  if (message.action === 'incrementCounter') {
    // Content script says user clicked a button

    // Get current count from storage
    chrome.storage.local.get('clickCount', (result) => {
      const newCount = (result.clickCount || 0) + 1;

      // Save new count
      chrome.storage.local.set({ clickCount: newCount }, () => {
        console.log('Counter incremented to:', newCount);

        // Send back the new count
        sendResponse({ success: true, count: newCount });

        // Update badge on extension icon to show count
        chrome.action.setBadgeText({ text: String(newCount) });
        chrome.action.setBadgeBackgroundColor({ color: '#4285f4' });
      });
    });

    return true;  // CRITICAL: Keeps message channel open for async sendResponse
  }

  if (message.action === 'getCount') {
    // Popup wants to know current count
    chrome.storage.local.get('clickCount', (result) => {
      sendResponse({ count: result.clickCount || 0 });
    });
    return true;
  }

  if (message.action === 'resetCounter') {
    // Options page wants to reset counter
    chrome.storage.local.set({ clickCount: 0 }, () => {
      console.log('Counter reset');
      chrome.action.setBadgeText({ text: '' });
      sendResponse({ success: true });
    });
    return true;
  }
});

// Listen for storage changes (when options page updates settings)
chrome.storage.onChanged.addListener((changes, areaName) => {
  console.log('Storage changed in area:', areaName);

  if (changes.highlightColor) {
    console.log('Highlight color changed from',
      changes.highlightColor.oldValue,
      'to',
      changes.highlightColor.newValue
    );

    // Send message to all tabs to update their highlight color
    chrome.tabs.query({}, (tabs) => {
      tabs.forEach(tab => {
        chrome.tabs.sendMessage(tab.id, {
          action: 'updateColor',
          color: changes.highlightColor.newValue
        }).catch(() => {
          // Tab might not have content script loaded, ignore error
        });
      });
    });
  }
});

content.js

Purpose: Runs inside web pages, can read/modify page DOM. This is the "hands" - can touch page but limited Chrome API access.

console.log('Content script loaded on:', window.location.href);

// Store current highlight color
let highlightColor = '#ffff00';

// Load highlight color from storage when script loads
chrome.storage.local.get('highlightColor', (result) => {
  if (result.highlightColor) {
    highlightColor = result.highlightColor;
    console.log('Loaded highlight color:', highlightColor);
  }
});

// Listen for clicks on the entire page
document.addEventListener('click', (event) => {
  // Only care about button clicks
  const target = event.target;

  // Check if clicked element is a button or looks like one
  if (target.tagName === 'BUTTON' ||
      target.tagName === 'INPUT' && target.type === 'button' ||
      target.tagName === 'INPUT' && target.type === 'submit') {

    console.log('Button clicked:', target.textContent || target.value);

    // Highlight the button
    highlightButton(target);

    // Tell background script to increment counter
    chrome.runtime.sendMessage(
      { action: 'incrementCounter' },
      (response) => {
        if (response && response.success) {
          console.log('Counter now at:', response.count);

          // Show temporary notification on page
          showNotification(`Button clicks: ${response.count}`);
        }
      }
    );
  }
});

// Function to highlight a button
function highlightButton(button) {
  // Save original background color
  const originalBg = button.style.backgroundColor;

  // Apply highlight
  button.style.backgroundColor = highlightColor;
  button.style.transition = 'background-color 0.3s';

  // Remove highlight after 1 second
  setTimeout(() => {
    button.style.backgroundColor = originalBg;
  }, 1000);
}

// Function to show notification on page
function showNotification(message) {
  // Create notification element
  const notification = document.createElement('div');
  notification.textContent = message;

  // Style it
  notification.style.cssText = `
    position: fixed;
    top: 20px;
    right: 20px;
    background: #4285f4;
    color: white;
    padding: 15px 20px;
    border-radius: 5px;
    font-family: Arial, sans-serif;
    font-size: 14px;
    z-index: 999999;
    box-shadow: 0 2px 10px rgba(0,0,0,0.3);
    animation: slideIn 0.3s;
  `;

  // Add to page
  document.body.appendChild(notification);

  // Remove after 2 seconds
  setTimeout(() => {
    notification.style.animation = 'slideOut 0.3s';
    setTimeout(() => notification.remove(), 300);
  }, 2000);
}

// Listen for messages from background (e.g., color updates)
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
  console.log('Content script received message:', message);

  if (message.action === 'updateColor') {
    // Options page changed the highlight color
    highlightColor = message.color;
    console.log('Highlight color updated to:', highlightColor);
  }

  if (message.action === 'getPageInfo') {
    // Popup wants info about current page
    sendResponse({
      url: window.location.href,
      title: document.title,
      buttonCount: document.querySelectorAll('button').length
    });
  }
});

popup.html

Purpose: UI that shows when clicking extension icon

<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <style>
    body {
      width: 300px;
      padding: 20px;
      font-family: Arial, sans-serif;
      margin: 0;
    }

    h1 {
      font-size: 18px;
      margin: 0 0 15px 0;
      color: #333;
    }

    .counter {
      font-size: 48px;
      font-weight: bold;
      color: #4285f4;
      text-align: center;
      margin: 20px 0;
    }

    .info {
      background: #f5f5f5;
      padding: 10px;
      border-radius: 4px;
      margin: 10px 0;
      font-size: 12px;
      color: #666;
    }

    button {
      width: 100%;
      padding: 10px;
      background: #4285f4;
      color: white;
      border: none;
      border-radius: 4px;
      cursor: pointer;
      font-size: 14px;
      margin-top: 10px;
    }

    button:hover {
      background: #3367d6;
    }

    .link {
      text-align: center;
      margin-top: 15px;
    }

    .link a {
      color: #4285f4;
      text-decoration: none;
      font-size: 12px;
    }
  </style>
</head>
<body>
  <h1>🖱️ Click Counter</h1>

  <div class="counter" id="counter">0</div>

  <div class="info" id="pageInfo">
    Loading page info...
  </div>

  <button id="refreshBtn">🔄 Refresh Count</button>

  <div class="link">
    <a href="#" id="optionsLink">⚙️ Settings</a>
  </div>

  <script src="popup.js"></script>
</body>
</html>

popup.js

Purpose: Logic for popup UI. Popup reloads every time you open it - no persistent state here.

// Get DOM elements
const counterDisplay = document.getElementById('counter');
const pageInfo = document.getElementById('pageInfo');
const refreshBtn = document.getElementById('refreshBtn');
const optionsLink = document.getElementById('optionsLink');

// When popup loads, get current count
document.addEventListener('DOMContentLoaded', async () => {
  console.log('Popup opened');

  await loadCounter();
  await loadPageInfo();
});

// Function to load counter value
async function loadCounter() {
  chrome.runtime.sendMessage({ action: 'getCount' }, (response) => {
    if (response) {
      counterDisplay.textContent = response.count;
      console.log('Loaded count:', response.count);
    }
  });
}

// Function to load page info
async function loadPageInfo() {
  const [tab] = await chrome.tabs.query({ active: true, currentWindow: true });

  if (!tab) {
    pageInfo.textContent = 'No active tab';
    return;
  }

  chrome.tabs.sendMessage(tab.id, { action: 'getPageInfo' }, (response) => {
    if (chrome.runtime.lastError) {
      pageInfo.textContent = 'Content script not available on this page';
      return;
    }

    if (response) {
      pageInfo.innerHTML = `
        <strong>Page:</strong> ${response.title}<br>
        <strong>Buttons:</strong> ${response.buttonCount}
      `;
    }
  });
}

// Refresh button click handler
refreshBtn.addEventListener('click', async () => {
  console.log('Refresh clicked');
  await loadCounter();
  await loadPageInfo();
});

// Options link click handler
optionsLink.addEventListener('click', (e) => {
  e.preventDefault();

  chrome.runtime.openOptionsPage();
  window.close();
});

options.html

Purpose: Settings page for the extension

<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <title>Click Counter Settings</title>
  <style>
    body {
      font-family: Arial, sans-serif;
      max-width: 500px;
      margin: 40px auto;
      padding: 20px;
      background: #f5f5f5;
    }

    .container {
      background: white;
      padding: 30px;
      border-radius: 8px;
      box-shadow: 0 2px 8px rgba(0,0,0,0.1);
    }

    h1 {
      margin-top: 0;
      color: #333;
    }

    .setting {
      margin: 20px 0;
    }

    label {
      display: block;
      margin-bottom: 8px;
      font-weight: 500;
      color: #555;
    }

    input[type="color"] {
      width: 100px;
      height: 40px;
      border: 1px solid #ddd;
      border-radius: 4px;
      cursor: pointer;
    }

    .current-count {
      background: #e3f2fd;
      padding: 15px;
      border-radius: 4px;
      margin: 20px 0;
      font-size: 18px;
      text-align: center;
      color: #1976d2;
    }

    button {
      padding: 10px 20px;
      border: none;
      border-radius: 4px;
      cursor: pointer;
      font-size: 14px;
      margin-right: 10px;
    }

    .save-btn {
      background: #4285f4;
      color: white;
    }

    .save-btn:hover {
      background: #3367d6;
    }

    .reset-btn {
      background: #f44336;
      color: white;
    }

    .reset-btn:hover {
      background: #d32f2f;
    }

    .status {
      margin-top: 15px;
      padding: 10px;
      border-radius: 4px;
      display: none;
    }

    .status.show {
      display: block;
    }

    .status.success {
      background: #d4edda;
      color: #155724;
    }
  </style>
</head>
<body>
  <div class="container">
    <h1>⚙️ Click Counter Settings</h1>

    <div class="current-count">
      Total clicks: <strong id="totalCount">0</strong>
    </div>

    <div class="setting">
      <label for="colorPicker">Highlight Color:</label>
      <input type="color" id="colorPicker" value="#ffff00">
      <small style="color: #666; margin-left: 10px;">
        Color used to highlight clicked buttons
      </small>
    </div>

    <div>
      <button class="save-btn" id="saveBtn">💾 Save Settings</button>
      <button class="reset-btn" id="resetBtn">🔄 Reset Counter</button>
    </div>

    <div id="status" class="status"></div>
  </div>

  <script src="options.js"></script>
</body>
</html>

options.js

Purpose: Logic for options page

// Get DOM elements
const colorPicker = document.getElementById('colorPicker');
const totalCount = document.getElementById('totalCount');
const saveBtn = document.getElementById('saveBtn');
const resetBtn = document.getElementById('resetBtn');
const status = document.getElementById('status');

// Load current settings when page opens
document.addEventListener('DOMContentLoaded', loadSettings);

saveBtn.addEventListener('click', saveSettings);
resetBtn.addEventListener('click', resetCounter);

// Function to load settings from storage
function loadSettings() {
  console.log('Loading settings...');

  chrome.storage.local.get(['highlightColor', 'clickCount'], (result) => {
    console.log('Loaded settings:', result);

    if (result.highlightColor) {
      colorPicker.value = result.highlightColor;
    }

    totalCount.textContent = result.clickCount || 0;
  });
}

// Function to save settings
function saveSettings() {
  console.log('Saving settings...');

  const newColor = colorPicker.value;

  chrome.storage.local.set({ highlightColor: newColor }, () => {
    console.log('Settings saved:', newColor);
    showStatus('Settings saved!');
  });
}

// Function to reset counter
function resetCounter() {
  if (!confirm('Reset click counter to 0?')) {
    return;
  }

  console.log('Resetting counter...');

  chrome.runtime.sendMessage({ action: 'resetCounter' }, (response) => {
    if (response && response.success) {
      console.log('Counter reset');
      totalCount.textContent = '0';
      showStatus('Counter reset to 0');
    }
  });
}

// Function to show status message
function showStatus(message) {
  status.textContent = message;
  status.className = 'status success show';

  setTimeout(() => {
    status.className = 'status';
  }, 3000);
}

// Listen for storage changes
chrome.storage.onChanged.addListener((changes, areaName) => {
  if (changes.clickCount) {
    totalCount.textContent = changes.clickCount.newValue;
    console.log('Counter updated to:', changes.clickCount.newValue);
  }
});

Testing Instructions

1. Create Files

  • Create a folder called "click-counter-extension"
  • Create all files listed above
  • Create 3 icon files (or use any small PNG images)

2. Load Extension

  1. Open Chrome
  2. Go to chrome://extensions
  3. Enable "Developer mode" (top right)
  4. Click "Load unpacked"
  5. Select your "click-counter-extension" folder
  6. Extension should appear with your icon

3. Test It

  1. Go to any website (e.g., google.com)
  2. Click any button on the page
  3. Button should flash yellow
  4. Notification should appear: "Button clicks: 1"
  5. Extension icon should show badge "1"

4. Check Popup

  1. Click extension icon in toolbar
  2. Popup should show counter and page info
  3. Click "Refresh Count" - counter updates

5. Check Options

  1. Click "Settings" in popup
  2. Options page opens
  3. Shows total count
  4. Change highlight color (e.g., to red #ff0000)
  5. Click "Save Settings"
  6. Go back to website, click button - should flash red now
  7. Click "Reset Counter" - counter goes to 0

6. Debugging

  • Background script console: chrome://extensions → Click "service worker"
  • Content script console: Right-click page → Inspect → Console tab
  • Popup console: Right-click popup → Inspect

7. What to Watch

  • Click button on page → Check page console for "Button clicked"
  • Check background console for "Counter incremented"
  • Check storage: chrome://extensions → Click "Details" → "Inspect views: service worker" → Application tab → Storage → Local Storage
  • Should see clickCount and highlightColor

8. Tracing a Click

page button click
→ content.js detects (line with addEventListener)
→ sends message to background (chrome.runtime.sendMessage)
→ background.js receives (onMessage.addListener)
→ background increments counter in storage
→ background sends response back to content
→ content shows notification
→ background updates badge on icon
→ open popup → popup asks background for count
→ displays in popup UI