Can a click event be used to open the sidebar

I’m missing the config.json file here.

Ah! I have a hidden config file that is not uploaded to GitHub, so I guess it does require a web-ext build! How can I send you the file? I had to use a CORS API to fetch favicons. The config file just has a URL and API key. I’m hosting the code on Google Cloud platform and it could become costly if used by a crazy bot.

Oh, I see, don’t send me your config with the API keys.
Fix the code so that people can test your addon without it :slight_smile:.
Or add there a dummy one with a fake API key. Whatever is easier…

I’m paying to host the code on GCP. I wouldn’t want it to be used for other purposes than using the extension.

config.json file is simply:

{
“API_URL”: “https://url”,
“API_KEY”: “password”
}

Maybe you can make something up, but the favicons might end up missing!

I’ve fixed that already with:

const config = await fetchConfig().catch(err => ({API_URL: '', API_KEY: ''}));

Now it works, but I forgot what I actually wanted to test :smiley:.
And I’ll need to sleep soon… so see you tomorrow? :slight_smile:

Have a good night’s rest and thank you for your help.

Can you share more about why you’d like the sidebar to have a tabId? What else do you feel is missing?

There’s no single decision maker, but I’m one of the people that’s often “in the room” as it were. I’m a developer relations engineer which basically means I try to make it easier for folks like you to build and maintain browser extensions.

I’m sorry to hear you feel like it’s not currently transparent or open to discussion. We really don’t try to hide anything or avoid discussions. For what it’s worth, I also had trouble figuring out how I could do my part to influence browser development before I started working on web browsers. (It can still be hard sometimes, though :sweat_smile:)

The two main places that WebExtensions capabilities are discussed in detail are browser issue trackers (Chromium, Firefox, Safari) and the WebExtensions Community Group’s GitHub repository. I also try to post here as often as I can to answer questions, share knowledge, and generally help out where possible.

While I work on Firefox Add-ons, I don’t have enough historical context to speak on behalf of Mozilla here. As such, what I’m about to say is my personal opinion.

IMO, the reason that browsers don’t allow extensions to change which side the sidebar appears on is because the sidebar is a browser feature, not an extension one. Sidebars are used for more than the capabilities that one extension chooses to expose in it’s sidebar view. In Firefox, the sidebar is also used for bookmarks, history, synced tabs, and more may be coming in the future (vertical tabs, anyone?). Chrome has a similar set of uses for their “side panel.”

UI can be quite hard to do well. As I see it, one of the most important things in the world of UI design is predictability. Users need to be able to form an accurate mental model for how their actions affect the software they’re using. This enables them to predict how the software will behave in the future and figure out what happened when they see the software in an unexpected state. Changing the position of the sidebar has some pretty big knock on effects for how the end user interacts with and thinks about the sidebar itself and the capabilities it exposes.

As a result, browsers need to be very careful with (and ultimately limit) how extensions are able to modify the size and position of the sidebar. If the user wants to change the sidebar’s position, they can do so. But that’s the user’s choice, not an extension author’s.

All that said, I’m personally supportive of the idea of exposing a property or method to detect which side of the window the sidebar is on. While not all extensions need to detect this, some do and currently can’t.

webextensions-polyfill is mostly focused on bringing promise support to Chrome. Since of Chrome’s MV3 APIs now support promises, there isn’t much benefit to including the polyfill. You can bridge the difference between the global objects by adding this one line to the top your files:

globalThis.browser ??= chrome;

This line checks if there’s a global variable named browser. If there is, we leave it alone. If the variable doesn’t exist or it exists but it’s value is null or undefined, we assign the value of the chrome global variable to it.

In other words, it’s effectively the same as:

if (typeof browser === "undefined") {
  var browser = chrome;
}

See MDN’s documentation nullish coalescing assignment for more info on how ??= works.

1 Like

Note that the webextension-polyfill allows onMessage handler return a Promise (which is resolved and send back to the caller), which is somehow still missing in Chrome:
https://issues.chromium.org/issues/40753031

This allows super simple message handler, for example:

browser.runtime.onMessage.addListener((data: RuntimeMessage, sender): RuntimeMessageResult => {
  console.log('BACKGROUND received message: ', data, sender);
  switch (data.type) {
    case 'window_resized': saveNewWindowSize(sender.tab?.windowId); break;
    case 'selfMessage':    return sender.tab?.id ? sendTabMessage(sender.tab.id, data.message) : sendMessage(data.message);
    case 'lastSelection':  return Promise.resolve(popLastSelection());
    case 'getSize':        return computeSizeAsync();
  }
});

See? Super simple way to react only to some messages, so it won’t “consume” messages of other handlers.
And I can return async function call if the sender expects a result.
This is much simpler than returning a “true” and then passing down the “sendResponse” callback.

1 Like

Maybe there’s more transparency than I imagined but I didn’t know where to look! I was totally unaware that Mozilla had developer relations engineers like yourself. That’s great news.

the sidebar is a browser feature, not an extension one

Personally, I don’t really see why this distinction should be made. What matters to me the most is to give users a good UX. I hope you’ll agree that extensions that provide a good UX contribute to a greater adoption of Firefox. It really touches me when a user tells me that they’re sticking to Firefox because of my extension. It makes me feel that I’ve completed my mission!

Concerning the UX pertaining to the sidebar, there’s a lot of room for improvement. You’d be surprised to hear how many users don’t know that there’s a sidebar! Others don’t know that or how it can be placed to the left or right of the browser. Constraining features for developers means that we cannot give our users the UX that would make them come back. I can give you 2 examples where I feel I’m giving users aa poor UX due to limitations with the sidebar:

  • my extenssion provides users with a grid of icons, each representing a different search engine that they frequently use. This is implemented through a content script. When a user clicks on one of thee grid’s icons, I can’t programmatically open up the sidebar to show the search results because it doesn’t correspond to the users action that browser.sidebarAction.open() would respond to. This leaves users thinking that something isn’t working because they have to manually open the sidebar to see the search results;

  • another poor UX is caused by not being able to set the width of the sidebar programmatically. If I’m not mistaken, the first time the sidebar opens, it’s only a few cm wide. It’s too narrow to conveniently display search results, even if a user is merely looking up a word definition in an online dictionary. It’s even worse if the user is interacting with an AI chat agent. Personally, I think the minimum width should correspond to that of a standard smartphone. But, here again, the user needs to manually set the width of the sidebar;

  • concerning the position of the sidebar being on the left or right of the browser, because many users don’t know that this is possible or how to do it, I feel that it just makes sense to give them a setting in the Options page of an extension where they can specify before hand where the sidebar should be and then forget about it. The user stays in control.

You also asked me in what circumstances a tabId for the sidebar would be useful. In the case of the grid of icons I mentioned above, I have a function in the content script that sends a message to the background script informing it the the content script has finished loading in the sidebar:

sendMessage({ action: 'sidebarContentLoaded', data: { tabUrl } });

The background script knows that the message is sent from the content script in the sidebar because the tabUrl is defined as follows:

let tabUrl = url + '#_sidebar';

However, I can’t send a message back from the background script to the content script in the sidebar to trigger a new action (e.g. insert a prompt in the textarea of an AI chat web page) because sending a message requires having a tabId.

Finally, thank you for clarifying things about the webextension-polyfill. I also hadn’t heard of nullish coalescing, but I don’t think that I can use it because Chrome only understands ‘chrome.’ and not ‘browser.’. Maybe the simplest here is just to replace all instances of ‘browse.’ with ‘chrome.’. I understand that this would also work in Firefox now.

I don’t think that any of my message handlers return a response. I’ve had issues sending responses in the past so I avoid using them and I’m not very good at dealing with promises. I can’t wrap my mind around their inner workings! :upside_down_face:

You HAVE TO use promises, it’s an amazing feature that makes asynchronous API a piece of cake. And since most browser API is async, it’s a must for readable code.

Check this simple tutorial:

I use async and await a lot. I’m not sure where I should use promises, but I do have a message handler of course.

Have you had the chance to see if you come across any bugs on Windows with my extension? I don’t dare make a new release if I’m not certain the bugs have been ironed out!

I’ve checked it only briefly, I have no idea what to test… Also I have no idea what’s normal and what is not. For example, the fact that all search results are opened in a new NOT focused tab feels totally like a bug :smiley:.
Also, for a moment it was opening two new tabs instead of one, but I don’t know how I did it and now I can’t reproduce it…
Do you have code that checks if the content script is already running in a tab to prevent running multiple instances?

You should have an automated e2e tests :slight_smile:.
I know exactly the feeling of doing a release and not being sure if it works or not. And as everyone else, I hate writing end to end tests. But it saved me so many times already, it was totally worth it!

If you type ‘cs .’ in the address bar and then hit Enter, it will take you to the Options page where you can decide if you want the search results to open in a new tab (amongst other options) and the tab to be active.

Do you have code that checks if the content script is already running in a tab to prevent running multiple instances?

I’m afraid not!

I’ve never written any tests and don’t know where or how to start. Do you have an extension with modules and tests on GitHub that could inspire me?

I’m afraid my e2e tests are not well suited for your case.

Not only they only work in Chrome (at that time, “puppeteer” didn’t have support for Firefox, I think they’ve added it only recently), but testing “context menu” clicks doesn’t work at all, at least at that time I couldn’t auto-click context menu items.

Also, I don’t have any public repositories, since this is my full-time (plus free time) job that I try to monetize, I’m very protective of my IP :slight_smile: (to compensate that, I’m trying to help here :slight_smile:).

But having tests before making big refactoring is very useful.
Unless you are in hurry with the release, and if you can afford spending time, I would definitely recommend writing tests now.

BTW, do you have a normal job? Or is this addon earning enough? :slight_smile:

I don’t have a job and just get by thanks to social security. It’s really hard to get a job in Switzerland nowadays, especially if you’re above 50 years old. I first created the extension for fun and to learn. Then I thought, having put so many hours into it, I’d like to try and monetize my extension. But that hasn’t worked out too well! So far, donations accumulated add up to about $260. I used to have 5’000 users, but it’s dwindled down to about 3’800 users today. The only time I had done any advertising for the extension was when Google+ was around… quite some time ago! I don’t really know how to monetize my extension. How do you monetize yours? Do you live off your extensions? Mind you, I only have one extension and I learned to code as a hobby so I’m not as proficient as you are.

Also, another dev (better than myself) created a similar extension to mine a few months after I had released mine. I noticed that each time I implemented something new, it would get copied. It turned out that users would ask the other dev to add a new feature after they had used it on my extension. So, I understand that you’re being protective with your IP.

1 Like

If I knew how to monetize my extensions, I would have done it long ago :smiley:.
But actually I managed to do it with my speed dial extension, it has PRO features locked behind a monthly payment through the Patreon page.

So I’m actually doing fine now (after ~6 years of minimal income), since living in Slovakia is “cheaper” than western countries.
But still, having a normal programming job I would make at last 4 times more :frowning:.

Anyway, this kind of monetizing is super complicated, and I’m still struggling monetizing all of my other extensions. Plus making it legal (taxes) and doing marketing, this is really not for me…

Oh man, now I’m thinking again about getting a normal job :smiley:.

At one time, I thought of using the following service which looks really easy to use. You might find it useful: https://extensionpay.com/

Yes, I’m aware of that. It helps a bit, because you don’t have to use your own server, nor integrate with Stripe (the payment system).
But it doesn’t help with taxes :upside_down_face:. And since your “customers” are from all around the world, to make it a legal income, you need to tax them in the country they are from, or something like that.

That’s why I ended up using Patreon, because they handle it, but the integration I had to build on my server took quite some time to implement.

Oh, there is blog from a famous dev. about it that may help:

1 Like

I agree that there’s room for improvement. IMO the sidebar work happening along with the experimental vertical tabs implementation improves the discoverability of extensions in the sidebar and sidebar customizations. If you haven’t tried it yet, I’d strongly encourage you to read the blog post and try it out in Firefox Nightly. I should also note that my current thinking about Firefox’s sidebar assumes that this new design is enabled by default.

I’d argue that “good UX” is also what browsers are concerned about, but that developers and platform maintainers often come at this from different perspectives. Developers are often focused on how well they can execute their vision of the experience they want to build and (hopefully) their own software’s security practices. Platform maintainers try to balance a variety of concerns including, but not limited to, the performance of a feature, it’s abuse scenarios, the ability to detect and prevent that abuse, and how well those capabilities fit into the broader product. And of course, users don’t care about any of that, they just want things to work and to work well. I should also say that no one of these perspectives is correct or complete.

A core tension of platform design and development is that any tool you give to a good dev who will reinvent the experience of using that platform is also one you give to a developer that will do everything in their power to lie to, cheat, and steal from your users. Sometimes the best user experience the platform provider can deliver is a compromised one.

FWIW this is a use case I’d also like to see supported, but it’s not clear how to do that responsibly.

Right now only a limited set of interactions in Firefox can be used to open the sidebar. I think that list is: browser/page action click, command execution, and (context) menu click. That’s because the browser wants to ensure that the sidebar is only opened in response to the user explicitly choosing to interact with the extension, and there are very few reliable signals for that.

In Chrome it’s possible to perform this action by clicking on a button in the page. That’s because Chrome curries the user gesture flag from the page’s event listener to the extension’s background context. But that approach also has a major downside: how do you know that the user was actually interacting with the extension? Event listeners can be registered on any DOM element including the HTML document itself. A passive event listener on the document (or other relevant ancestor node) can easily be used to capture a legitimate user interaction that had nothing to do with the extension. That’s a very realistic and easy to execute abuse scenario.

At the moment abuse of such capabilities is relatively limited, which is do in no small part to browsers being cautious about what features should be gated behind a user interaction. In Firefox, I think the current list of APIs that require user interactions is: permissions requests, opening a popup, opening/closing the sidebar, opening a downloaded file, and installing an extension. These aren’t all equally dangerous, but they all present risks that browsers aren’t comfortable giving extensions outright. It would also present a significant risk to users if all of these could be triggered by user interactions on any web page.

Thanks for breaking that down. This is an interesting case because content scripts in the sidebar are handled inconsistently across browsers; Firefox injects them while Chrome does not. As a generic solution, this case feels like it would be best served by using a documentId to message to the correct frame. documentId is a concept we’ve been discussing in the WebExtensions Community Group that would allow extensions to message specific frames in a page rather than the entire page. It also helps address race conditions related to exchanging messages with a tab during navigations.

Have you tried using the sendResponse parameter of the runtime.onMessage event listener to reply to the content script message?

You can use it in Chrome. The point of the snippet is to make it so you can use browser everywhere. :wink:

1 Like