I have a Chrome extension, that has a snip tool I've been working on. Its almost working as I'd like, the issue is it seems that as soon as I load a webpage it executes the whole process, where I'm intending to have the user click a button and then it starts the process.
So, I've based on this on the CaptureVisibleTab API and I have to communicate between some scripts to do this.
To start my manifest.json (relevant info)
"manifest_version": 3,
permissions": [
"activeTab",
"tabs",
"contextMenus",
"scripting",
"storage",
"identity"
],
"background": {
"service_worker": "scripts/background.js"
},
"content_scripts": [{
"matches": ["<all_urls>"],
"js": ["scripts/content.js"],
"all_frames": false,
"run_at": "document_end"
I have a button on the popup.HTML that has a id called "snip", pretty self explanitory
then in the popup.js there's a variable for the snip and a event listener to then que the screenshot
var snip = document.getElementById("snip");
snip.addEventListener("click", function() {
chrome.runtime.sendMessage({ action: "captureVisibleTab" }, function(response) {
console.log(response.imageUrl);
});
});
Now in background.js it has a listener for the action and just returns the image result to then be cropped, and sent to the content script
chrome.runtime.onMessage.addListener(function(request, sender, sendResponse) {
if (request.action === "captureVisibleTab") {
chrome.tabs.captureVisibleTab(null, { format: "png" }, function(imageUrl) {
sendResponse({ imageUrl: imageUrl });
});
return true; // Required to indicate that the response will be sent asynchronously
}
});
finally in the content.js, I just have the function and the message for the captureVisibleTab where when it gets it, it takes the image of the webapge and passes it as a argument in the function
function cropScreenshot(imageUrl) {
var canvas = document.createElement("canvas");
var ctx = canvas.getContext("2d");
var isMouseDown = false;
var startX, startY, endX, endY;
var img = new Image();
img.onload = function() {
canvas.width = img.width;
canvas.height = img.height;
ctx.drawImage(img, 0, 0);
canvas.style.display = "block";
var overlay = document.createElement("div");
overlay.style.position = "absolute";
overlay.style.top = "0";
overlay.style.left = "0";
overlay.style.width = canvas.width + "px";
overlay.style.height = canvas.height + "px";
overlay.style.background = "rgba(0,0,0,0.4)";
document.body.appendChild(overlay);
overlay.addEventListener("mousedown", function(e) {
isMouseDown = true;
startX = e.offsetX;
startY = e.offsetY;
});
overlay.addEventListener("mouseup", function(e) {
isMouseDown = false;
endX = e.offsetX;
endY = e.offsetY;
var width = endX - startX;
var height = endY - startY;
if (width > 0 && height > 0) {
var croppedCanvas = document.createElement("canvas");
var croppedCtx = croppedCanvas.getContext("2d");
croppedCanvas.width = width;
croppedCanvas.height = height;
croppedCtx.drawImage(canvas, startX, startY, width, height, 0, 0, width, height);
var downloadLink = document.createElement("a");
downloadLink.href = croppedCanvas.toDataURL("image/png");
downloadLink.download = "snipped-img.png";
// Attach the anchor to the document and click it
document.body.appendChild(downloadLink);
downloadLink.click();
// Remove the anchor from the document
document.body.removeChild(downloadLink);
}
canvas.style.display = "none";
overlay.remove();
});
overlay.addEventListener("mousemove", function(e) {
if (isMouseDown) {
var width = e.offsetX - startX;
var height = e.offsetY - startY;
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.drawImage(img, 0, 0);
ctx.fillStyle = "rgba(255,255,255,0.5)";
ctx.fillRect(startX, startY, width, height);
}
});
};
img.src = imageUrl;
}
chrome.runtime.sendMessage({ action: "captureVisibleTab" }, function(response) {
if (response && response.imageUrl) {
// Call the cropScreenshot function and pass the imageUrl as a parameter
var imageUrl = response.imageUrl
cropScreenshot(imageUrl);
} else {
console.log("Error capturing visible tab");
}
});
that is the gist of it, I have tried putting a action between popup and background, like startSnip, but that doesn't work. This as it sits is currently the closest I've come to getting it right. So I'm sure its something obvious now, and I just can't see it. any help is greatly appreciated
Simplified Messaging
As others have already pointed out, the main problem is with your content script. The captureVisibleTab method is called immediately on page load and not when a button is clicked. You can fix this and also simplify your code as shown.
The code below has been tested and works
background.js
You can remove the popup completely and instead use the onClicked event. This way users only need to click on the extension icon to capture the current tab. This can be done by clearing the default popup handler and adding your own handler as shown:
// Allows onClicked to fire when popup was set in manifest
chrome.action.setPopup({ popup: "" });
chrome.action.onClicked.addListener((tab) => {
chrome.tabs
.captureVisibleTab(tab.windowId, { format: "png" })
.then((dataUrl) => {
chrome.tabs.sendMessage(tab.id, {
action: "captureVisibleTab",
tabId: tab.id,
length: dataUrl.length,
dataUrl: dataUrl,
});
});
});
content.js
And to the content script add a listener to process the captured image:
chrome.runtime.onMessage.addListener((message) => {
if (message.action === "captureVisibleTab") {
// do something...
// cropScreenshot(message.dataUrl);
}
});
manifest.json
If the background script tries to send a message before the content script has loaded it will fail. There is no handler to receive the message. To avoid this problem you can modify run_at in the manifest to load the content script early. This way your capture button will still work even while the page is still loading ads, etc.
"content_scripts": [
{
"run_at": "document_start",
"js": ["scripts/content.js"],
... etc
Image Cropping
There are a number of good image cropping libraries available, like ImageCropper, Croppr, and Cropper. Each has different features and complexity. You may want to consider one of these in place of your custom code.