0

I want my Chrome extension to add an element to the right-click context menu, allowing the user to change the background color of the right-clicked element.

manifest.json:

{
    "manifest_version": 3,
    "name": "Mouse Tracker",
    "description": "Tracks the mouse position and adds a right click context menu item to get the cursor position and currently hovered element and change the background color of that element to red.",
    "default_locale": "en",
    "version": "1.0",
 
    "background": {
        "service_worker": "background.js"
    },
    "permissions": [
        "contextMenus",
        "activeTab"
    ],
    "content_scripts": [
        {
            "matches": ["<all_urls>"],
            "js": ["content.js"]
        }
    ]
}

background.js:

chrome.runtime.onInstalled.addListener(function () {
    chrome.contextMenus.create({
        id: "change-color",
        title: "Change background to red",
        contexts: ["all"]
    });
});

chrome.contextMenus.onClicked.addListener(function (info, tab) {
    if (info.menuItemId == "change-color") {
        chrome.tabs.query({ active: true, currentWindow: true }, function (tabs) {
            chrome.tabs.sendMessage(tabs[0].id, { command: "change_color" }, function (response) { });
        });
    }
});

I tried two approaches to my content.js file:

content.js #1:

let element;

//Detecting when an element is hovered
document.addEventListener('mouseover', function (e) {
    element = e.target
});

chrome.runtime.onMessage.addListener(function (request, sender, sendResponse) {
    if (request.command == "change_color") {
        sendResponse({ result: 'success' });
        element.style.backgroundColor = "red";
    }
});

I thought the mouseover events would stop while the mouse was within the right-click context menu. But instead, the element right clicked on is not the one selected (turned red). Rather, whatever element was beneath the cursor underneath the context menu is selected.

content.js #2: (suffers same exact problem)

let mouseX = 0;
let mouseY = 0;
let element;

document.onmousemove = function (e) {
    mouseX = e.clientX;
    mouseY = e.clientY;
    element = document.elementFromPoint(mouseX, mouseY);
    console.log(element)
}

chrome.runtime.onMessage.addListener(function (request, sender, sendResponse) {
    if (request.command == "change_color") {
        element.style.backgroundColor = "red";
        sendResponse({ result: 'success' });
    }
});

I thought I might be able to use the context menu API to detect when the menu opens and closes but that doesn't seem to be possible:

content.js #3:

let mouseX = 0;
let mouseY = 0;
let element;
let contextOpen = false

document.onmousemove = function (e) {
    if (!contextOpen) {
        mouseX = e.clientX;
        mouseY = e.clientY;
        element = document.elementFromPoint(mouseX, mouseY);
        console.log(element)
    } 
}

chrome.runtime.onMessage.addListener(function(request, sender, sendResponse) {
    if (request.command == "change_color") {
        element.style.backgroundColor = "red";
        sendResponse({result: 'success'});
    }
    if (request.command == "menu_open") {
        contextOpen = true
        sendResponse({result: 'success'});
    }
    if (request.command == "menu_close") {
        contextOpen = false
        sendResponse({result: 'success'});
    }
});

I could not seem to find an event to listen to within background.js to send those commands.

Is there any solution?

J.Todd
  • 707
  • 1
  • 12
  • 34
  • it was almost easy, but the close event handler being 'click' is unreliable. //Detect when menu is opened document.addEventListener('contextmenu', function(event) { console.log('Right Click Menu Opened'); }); //Detect when menu is closed document.addEventListener('click', function(event) { console.log('Right Click Menu Closed'); }); – J.Todd Feb 21 '23 at 15:49
  • This is perhaps related to your question although not an exact duplicate IMHO https://stackoverflow.com/q/6231052/125981 – Mark Schultheiss Feb 21 '23 at 15:51
  • @MarkSchultheiss it's a tantalizing problem because I bet someone has some magical solution, but I've been researching and thinking for an hour and it's beyond me so far. Detecting when the context menu closes (reliably) seems to be entirely impossible, as far as I can tell.. but maybe there's a trick to only track the mouse movement / hover when it's not on the context menu. – J.Todd Feb 21 '23 at 16:05
  • My understanding of the problem was flawed. It's not that the cursor events continue during the mouse hovering the context menu. They don't. What's actually happening is the moment the context menu button is clicked, the menu disappears and an additional move / hover event actually gets triggered (updating `element`) in the instant before the change propagates. So @MarkSchultheiss's linked question is actually more relevant than I knew. This probably just requires a tiny delay, perhaps even `setTimeout` of 0 to cycle the event loop. – J.Todd Feb 21 '23 at 16:15
  • 1
    FWIW this is NOT really a duplicate but sometimes I use data attributes to set a "state" "toggle" which can be more than an ON/OFF and show multiple states and just changing the dataset value changes the visual from the CSS example: https://stackoverflow.com/a/75405196/125981 – Mark Schultheiss Feb 21 '23 at 16:41

1 Answers1

0

THis fixes it. My analysis was initially wrong. Hover / mouse events do not continue while the context menu is hovered. What was actually happening was the context menu was closing and the selected element was updating the instant before the color change executed.

document.onmousemove = function (e) {
    setTimeout(() => {
        console.log(e)
        mouseX = e.clientX;
        mouseY = e.clientY;
        element = document.elementFromPoint(mouseX, mouseY);
        console.log(element)
    }, 100)
}
J.Todd
  • 707
  • 1
  • 12
  • 34