barshopen
barshopen

Reputation: 1311

Guaranteeing at most 1 chrome extension popup page open at a time

On my chrome extension, I have a popup page and a background script.

As default, when I click on the extension's icon in two different windows, a popup will open in both windows. I want to limit the amount of popups opened by the extension to be at most one at a time.

Here's how the full scenario that I'm trying to create:

  1. At first no pop up is activated.
  2. Window A opened a popup.
  3. Window B opened a popup, in which case, Window A's popup will close.
  4. Window C is created, go to 2, but this time Window A<-Window B and Window B<-Window C
  5. If in any time The only popup that is open was closed, return to 1.

I know that a popup windows was created because I have a simple port connection that is invoked on the popup startup. Thus, the background is in theory aware of all popup windows that are created, namely, that is the code that I run in the popup to connect:

const port = chrome.runtime.connect({ name: 'popup-communications' });

I attempted to solve the problem in 3 ways, all of them failed.

Attempt 1 Hold the last popup that was connected. If a new one appears, close the old one before you save the new one. Use chrome.extension.getViews to get the new port. I imagined this would work, but rapid clicks on the extension icon (to invoke browserAction) makes this popUp state confused.

let popUp;
chrome.runtime.onConnect.addListener(function connect(port) {
  if (port.name === 'popup-communications') {
    // attempt 1
    if (popUp) {
      popUp?.close?.();
      popUp = null;
      console.log('removed old pop up');
    }
    [popUp] = chrome.extension.getViews({
      type: 'popup',
    });
});

Attempt 2 Close all popups that received from chrome.extension.getView, but the last one. The problem with this approach is that the chrome.extension.getView does not guarantee any order.

let popUp;
chrome.runtime.onConnect.addListener(function connect(port) {
  if (port.name === 'popup-communications') {
    // attempt 2
    const popUps = chrome.extension.getViews({
      type: 'popup',
    });
    console.log(popUps);
    for (let i = 0; i < popUps.length - 1; i++) {
      popUps[i].close();
    }
});

I also experimented with chrome.browserAction.disable and chrome.browserAction.enable. This solution maintains indeed maintains 1 popup at all time, but I want it the popup to be available whenever I click on the extension icon, and this will not happen with this approach (instead I will need to find the relevant window with this popup)

Is there a way to achieve what I'm trying to do here?

Upvotes: 0

Views: 238

Answers (1)

Neea
Neea

Reputation: 1424

I was able to achieve this behavior in the following way.

background.js

The background listens to connecting popups.

When a popup connects, it will broadcast a message to all open popups to close. Note this message is not sent over the port since the port connection does not broadcast.

There should exist a way to optimize, since at most one other popup can be open, but I will leave that for another time and prefer not to create global variables.

chrome.runtime.onConnect.addListener(function connect(port) {
    if (port.name === 'popup-communications') {

        port.onMessage.addListener(function (msg) {
            if (msg.here) {
                const activeTabId = msg.here;

                // broadcast close request
                chrome.runtime.sendMessage({closeUnmatched: activeTabId});
            }
        });
    }
});

popup.js

  1. Perform a lookup of its tab id.

  2. Add a message listener to know when to close. If the message to close does not match current tab id, popup will close. window.close() is sufficient to close a popup.

  3. "announce" to background that popup is ready by sending the tab Id over the port.

async function getCurrentTab() {
    let queryOptions = {active: true, currentWindow: true};
    let [tab] = await chrome.tabs.query(queryOptions);
    return tab;
}

function addListener(myTabId) {
    chrome.runtime.onMessage.addListener(function (msg) {
        if (msg.closeUnmatched && msg.closeUnmatched !== myTabId) {
            window.close();
        }
    });
}

(async function constructor() {
    const port = chrome.runtime.connect({name: 'popup-communications'});

    // whoami lookup
    const {id: myTabId} = await getCurrentTab();

    // add handler to self-close
    addListener(myTabId);

    // tell background I'm here
    port.postMessage({here: myTabId});

    // do whatever with port..
}());

I assume steps 1-3 can be done faster than user switching tabs/windows to activate another popup. The port connection was complicating things, but I left it in the answer, since you may have a use case for it.

Upvotes: 1

Related Questions