Files
Greenlens/docs/hashnode-devto-posts/post-02-manifest-v3-chrome-extension-scanner.md
2026-08-05 19:39:22 +02:00

250 lines
9.8 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
title: "How to Build a Lightweight Chrome Extension (Manifest V3) for Real-Time Image & Canvas Inspection"
description: "A step-by-step developer guide to building a Manifest V3 browser extension with image scanning, Canvas extraction, context menu triggers, and shadow DOM overlays."
tags: ["chromeextension", "javascript", "webdev", "browser"]
canonical_url: "https://greenlenspro.com/"
cover_image: "https://greenlenspro.com/images/blog/chrome-extension-manifest-v3.jpg"
---
# How to Build a Lightweight Chrome Extension (Manifest V3) for Real-Time Image & Canvas Inspection
Browser extensions are one of the most effective ways to make AI models immediately accessible to users across the web. Instead of navigating to a dedicated web app, users can inspect images, scan elements (`pflanzen scanner`), or perform visual AI queries on any web page with a single right-click or hover action.
However, migrating to or building on **Chrome Extension Manifest V3 (MV3)** introduces strict security policies, background service worker lifecycles, and CORS restrictions that break traditional DOM scraping techniques.
In this deep-dive tutorial, we'll walk through the implementation of a zero-dependency Chrome extension inspired by the [GreenLens Chrome Extension](https://greenlenspro.com/). You will learn how to set up Manifest V3 service workers, extract cross-origin images or Canvas elements without triggering CORS errors, and render isolated hover UI overlays using the Shadow DOM.
---
## 1. Extension Architecture under Manifest V3
Under Manifest V3, background scripts no longer run persistently in a DOM-enabled background page. Instead, they operate as **ephemeral Service Workers** that spin down after periods of inactivity.
```mermaid
flowchart LR
A[DOM / Webpage Image] -->|Hover / Context Menu| B[Content Script `scan.js`]
B -->|Chrome Message Passing| C[MV3 Service Worker `background.js`]
C -->|API Fetch / Model Inference| D[Remote AI API / GreenLens Backend]
D -->|Diagnostic Payload| C
C -->|Message Response| E[Shadow DOM Overlay in `scan.js`]
```
### Key Components:
- **`manifest.json`**: Declares permissions (`contextMenus`, `activeTab`, `scripting`, `storage`).
- **`background.js` (Service Worker)**: Registers context menus, handles message queues, and dispatches external API requests.
- **`scan.js` (Content Script)**: Injected into host pages, inspects hovered elements, captures Canvas data, and renders Shadow DOM overlays.
- **`popup.js` / `popup.html`**: Extension popup UI for quick status checks and manual URL uploads.
---
## 2. Defining Manifest V3 (`manifest.json`)
To inspect image elements on web pages (`pflanze erkennen`), your manifest must declare explicit permissions for host access while maintaining minimal scope for Chrome Web Store approval.
```json
{
"manifest_version": 3,
"name": "GreenLens - Instant Plant Scanner & Disease Identifier",
"version": "1.0.1",
"description": "Scan any plant image or flower photo across the web to identify species and diagnose health symptoms.",
"permissions": [
"contextMenus",
"activeTab",
"storage"
],
"host_permissions": [
"https://*/*",
"http://*/*"
],
"background": {
"service_worker": "background.js",
"type": "module"
},
"content_scripts": [
{
"matches": ["<all_urls>"],
"js": ["shared.js", "scan.js"],
"css": ["scan.css"]
}
],
"action": {
"default_popup": "popup.html",
"default_icon": "icons/icon-48.png"
}
}
```
---
## 3. Background Service Worker & Context Menu Setup (`background.js`)
The service worker creates a custom context menu item when the user right-clicks any image on a website. When clicked, it passes the target image URL or data URI to your vision recognition backend.
```javascript
// background.js - MV3 Service Worker
chrome.runtime.onInstalled.addListener(() => {
chrome.contextMenus.create({
id: "greenlens-scan-image",
title: "🌱 Scan with GreenLens (Identify & Diagnose)",
contexts: ["image"]
});
});
chrome.contextMenus.onClicked.addListener(async (info, tab) => {
if (info.menuItemId === "greenlens-scan-image" && tab?.id) {
const imageUrl = info.srcUrl;
// Send message to content script to display loading state
chrome.tabs.sendMessage(tab.id, {
action: "INITIATE_SCAN",
imageUrl: imageUrl
});
try {
const response = await fetch("https://greenlenspro.com/v1/scan", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ imageUrl: imageUrl })
});
const data = await response.json();
// Dispatch results back to content script
chrome.tabs.sendMessage(tab.id, {
action: "RENDER_RESULT",
result: data
});
} catch (error) {
console.error("Scan API Error:", error);
chrome.tabs.sendMessage(tab.id, {
action: "SCAN_ERROR",
error: "Failed to scan target image."
});
}
}
});
```
---
## 4. Extracting Canvas & CORS Images in Content Script (`scan.js`)
Websites often render images inside `<canvas>` tags or block direct CORS fetching via `crossorigin="anonymous"`. To bypass CORS hurdles safely without proxying, content scripts can convert image elements into base64 Data URIs directly inside the client browser.
```javascript
// scan.js - Content Script Element Extraction
function convertElementToDataUri(imgElement) {
return new Promise((resolve, reject) => {
// Handling standard <img> tags
if (imgElement.tagName.toLowerCase() === 'img') {
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
canvas.width = imgElement.naturalWidth || imgElement.width;
canvas.height = imgElement.naturalHeight || imgElement.height;
try {
ctx.drawImage(imgElement, 0, 0);
resolve(canvas.toDataURL('image/jpeg', 0.85));
} catch (err) {
// Tainted canvas fallback: return src URL directly
resolve(imgElement.src);
}
return;
}
// Handling HTML5 Canvas elements
if (imgElement.tagName.toLowerCase() === 'canvas') {
try {
resolve(imgElement.toDataURL('image/jpeg', 0.85));
} catch (err) {
reject(new Error("Canvas tainted by cross-origin data."));
}
return;
}
reject(new Error("Unsupported element type for scanning."));
});
}
```
---
## 5. Isolated UI Overlays using Shadow DOM
Injecting popup UI overlays directly into arbitrary third-party web pages usually results in CSS style leaks. Global stylesheets from the host site can ruin your extension's typography, buttons, and layout.
The solution is wrapping your UI in an isolated **Shadow DOM root**.
```javascript
// scan.js - Isolated Shadow DOM Popup Injector
function injectResultModal(scanData) {
let hostElement = document.getElementById('greenlens-shadow-host');
if (!hostElement) {
hostElement = document.createElement('div');
hostElement.id = 'greenlens-shadow-host';
document.body.appendChild(hostElement);
}
// Create shadow root if it doesn't already exist
const shadowRoot = hostElement.shadowRoot || hostElement.attachShadow({ mode: 'open' });
shadowRoot.innerHTML = `
<style>
.gl-card {
position: fixed;
bottom: 24px;
right: 24px;
width: 320px;
background: #ffffff;
color: #16181d;
border-radius: 12px;
padding: 16px;
box-shadow: 0 10px 30px rgba(0,0,0,0.2);
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
z-index: 999999;
}
.gl-title { font-size: 16px; font-weight: 700; color: #16794a; margin: 0 0 6px; }
.gl-sub { font-size: 13px; color: #4a4f5a; margin-bottom: 12px; }
.gl-badge { display: inline-block; background: #e8f4ed; color: #16794a; padding: 4px 8px; border-radius: 6px; font-size: 11px; font-weight: 600; }
.gl-close { float: right; cursor: pointer; border: 0; background: none; font-size: 16px; color: #888; }
</style>
<div class="gl-card">
<button class="gl-close" onclick="this.getRootNode().host.remove()">×</button>
<div class="gl-badge">${scanData.confidence || '98% Match'}</div>
<h3 class="gl-title">${scanData.species || 'Pflanze erkannt'}</h3>
<p class="gl-sub">${scanData.diagnosis || 'Healthy foliage detected.'}</p>
<a href="https://greenlenspro.com" target="_blank" style="color:#16794a; font-weight:600; font-size:12px; text-decoration:none;">View complete care guide →</a>
</div>
`;
}
```
---
## 6. Comparing Chrome Extension Scanners vs. Google Lens
While generic visual search engines like `google lens pflanzen erkennen` return broad web image matches, a domain-tailored Chrome extension offers specialized capabilities:
| Feature | Generic Visual Search (`google lens`) | Specialized MV3 Extension (GreenLens) |
|---|---|---|
| **Species Identification (`pflanzen bestimmen`)** | Broad web search matches | Specialized Botanical Taxonomy Models |
| **Health Diagnosis (`pflanzenkrankheiten erkennen`)** | Limited to visual similarity | Multi-symptom chlorosis & pest detection |
| **Context Integration** | Opens external tab | In-page Shadow DOM Overlay |
| **Canvas & WebGL Support** | No direct element inspection | Client-side Data URI canvas extraction |
---
## Summary & Developer Checklist
1. **Adopt MV3 Service Workers:** Treat background tasks as stateless, event-driven functions.
2. **Prevent CSS Contamination:** Always use `attachShadow({ mode: 'open' })` for injected content script UI elements.
3. **Handle Canvas Fallbacks:** Gracefully degrade between DOM src attributes, canvas data URIs, and remote URLs.
4. **Minimal Permissions Strategy:** Request only `contextMenus` and `activeTab` to ensure rapid Chrome Web Store review.
To see a live implementation of an in-browser plant scanner, test out the [GreenLens Browser Extension Engine](https://greenlenspro.com/).