Invoking a Color Wheel from a Context Menu


(Matthew G. Saroff) #1

If it were a web page, I could simply use <input type="color"> to popup a browser native color picker.

It seems to me that it there should be a similarly simple way of doing this in a webextension, but I haven’t found it.

What I am looking to do is select some text, right click on it, select “font color” and have a color picker pop up, where the user would pick the color that they wanted.


(Niklas Gollenstede) #2

You can do this in a similar to selecting files from JS scripts: create the input element and emulate a click on it. Just keep in mind that browsers usually only allow stuff like this following user actions (mouse/keyboard events). I am not sure if a message handler following a commands API event qualifies (and I’m pretty sure you have to do this in a content script).

Here is the code (without any error handling):

function clickElement(element) {
	const evt = document.createEvent('MouseEvents');
	evt.initEvent('click', true, true);
	element.dispatchEvent(evt);
	return element;
}

function pickColor() { return new Promise(resolve => {
	const input = document.createElement('input'); input.type = 'color';
	input.addEventListener('change', () => resolve(input.value));
	clickElement.call(window, input);
}); }

someElement.onclick = async () => {
	const color = (await pickColor());
	console.log(color);
};

This worked in my test just now on this page. But it is something of a hack and I wouldn’t blindly assume browser or OS compatibility …


(Matthew G. Saroff) #3

Thanks.

BTW, do you know of a good source of information on error handling?

I am about 80% of the way through on features, and I definitely need some error handling, as sometimes, the extensions stops working, and I need to restart the browser.

Also, that code appears to trigger the popup blocker.


(Matthew G. Saroff) #4

I just realized that there is a simpler solution, since, for BBSes and the like, there are only about a dozen colors you need for font colors (the menu in question), I just add the colors to the menu, and it’s no more clicks than the XUL popup of the old extension:

I’m probably going to drop a few colors though.

(also, my badhair icons will be replaced by some sort of design by beta)


(Niklas Gollenstede) #5

The popup-blocker should only interfere if you call pickColor from outside a direct user interaction:

(mouse/keyboard events). I am not sure if a message handler following a commands API event qualifies

But, if you only need a few colors, the system color picker, which spits out 24bit colors, wouldn’t really work anyway.


(Matthew G. Saroff) #6

I got your code working, it shows the full native color dialogue, but only if I set to allow popups.

I catch the clicked menu:

browser.runtime.onMessage.addListener(function(commandString, sendResponse) {
    CommandParse(commandString);
});

then I send it to my CommandParse function:

async function CommandParse(argString) {
.......
        if (argString.includes("fontcol")) { // Invoke font color wheel
            argString = await getColor(argString);
              
        }

Which calls getColor, which invokes your functions, but somewhere or other the user click is being lost:

async function getColor(mkColor) {
    let fontColor = await pickColor();
    mkColor = mkColor.replace(/{{fontcol}}/g, fontColor);
    return mkColor;
}

Obviously, I did not encounter this in my other menu items because I did not need a popup window to invoke those functions.

The menu is invoked by a user action, clicking on the menu, but that gets lost somewhere in the handoff.


(Niklas Gollenstede) #7

The problem with the popup blocker is probably something nobody thought about, or something that isn’t implemented yet. I don’t think somebody decided that it should be like this.
You could look for and/or file a bug on bugzilla.


Error handling is not something you can add as a feature, it is (or should be) integrated in every part of your code.
Wherever you do something that could go wrong, make sure it throws an Error (and don’t ever throw strings or other stuff). Good APIs should already do this (but my proposed function doesn’t, which is why I mentioned it; it should throw if the dialog fails to open, as it currently is the case for you).
When everything nicely throws errors, you should decide on places to handle them. Some errors you can “recover” from (ignore them, try something else, …) and with others you can do little more than report them to the user.
Also, if your extension manages any kind of permanent state, make sure that thrown errors don’t bring you invalid states. (E.g. if you display some UI, but that fails somehow, remove that UI, instead of having something undefined hanging around.)