How to determine if side panel is open or not?

2024-07-01
#english #browser-extension #typescript

TL;DR, side panel is a new feature in chrome 114+ and MV3. However there is no official API to detect if side panel is open or not, but we can implement it by using chrome.runtime.connect

Chrome browser side panel UI

API for side panel

there are only four methods for side panel

  1. getOptions() Returns the active panel configuration.
  2. getPanelBehavior() Returns the extension's current side panel behavior
  3. open() Opens the side panel for the extension. This may only be called in response to a user action.
  4. setOptions() Configures the side panel.

for more details, you can check sidePanel#method.

it's clear that there no way to detect if the side panel is open or not. What's more, we have to implement the closing behavior ourselves. Though it's not a big deal, it's still a bit annoy.

Use cases

Our team is building an extension, we use short cuts to open a side panel on a web page. As you know, users sometimes create a new tab page and then enter the url, making a side panel on a new tab page doesn't make sense. So we hope side panel can be closed automatically on a new tab page and open on a web page, which means we need to know the state of side panel.

Key points

There are two key points need to be resolved:

  1. Whether the current page is a new tab or a web page
  2. The state of side panel

create or update a new tab

current page is a new tab or a web page ?

it's easy to determine if the current page is a new tab or just a web page. However, we'd better be cautious because there are many scenarios to consider:

  1. creating a tab
  2. activating a tab
  3. updating a tab

All of these scenarios tigger an event:

// background
chrome.tabs.onCreated.addListener(async (tab: chrome.tabs.Tab) => {  
  if (tab.pendingUrl == NEW_TAB_DEFAULT_URL) {  
	  ... 
  }
  ...
});

chrome.tabs.onActivated.addListener(async function (activeInfo: chrome.tabs.TabActiveInfo) {
	const tab = await chrome.tabs.get(activeInfo.tabId);  
	if (tab.url == NEW_TAB_DEFAULT_URL) {  
		...
	}
	... 
}
	
chrome.tabs.onUpdated.addListener((  
  _tabId: number,  
  changeInfo: chrome.tabs.TabChangeInfo,  
  tab: chrome.tabs.Tab,  
) {    
	if (tab.url == NEW_TAB_DEFAULT_URL) {  
		...
	}
	...
}

the state of side panel

Initially, the side panel is closed, and we'll refer to this state as sidePanelOpened in the rest of the article. This state can be changed by user action. When a shortcut is used, we set sidePanelOpened to true; otherwise, we close sidePanel and sendMessage to side panel. Meanwhile, we use chrome.runtime.onConnect to listen close event from side panel.All of this indicates that sidePanelOpened is crucial.

// background
let sidePanelOpened = false;

chrome.runtime.onConnect.addListener(function (port) {  
  if (port.name === SIDE_PANEL_PORT) {  
    port.onDisconnect.addListener(() => { 
      sidePanelOpened = false;  
    });  
  }  
});

  
chrome.commands.onCommand.addListener(async command => {  
  chrome.tabs.query({ currentWindow: true, active: true }, function (tabs) {  
    const tab = tabs[0];  
    const tabId = tab.id;  
    if (!sidePanelOpened) {  
      chrome.sidePanel.open({ tabId });  
      sidePanelOpened = true;  
    } else {  
      chrome.runtime.sendMessage({ message: CLOSE_SIDE_PANEL });  
    }  
  });  
});

// side panel
chrome.runtime.onMessage.addListener((request: { message: string }) => {  
  if (request.message === CLOSE_SIDE_PANEL) window.close();  
});  
  
chrome.runtime.connect({ name: SIDE_PANEL_PORT });

Combining tab events with sidePanelOpened

Now the last thing we need to do is that when the url of tab becomes chrome://newtab, we should attempt to close side panel if it is open.

So how can we determine if the side panel exists or not? luckily, we can use chrome.runtime.getContexts

export async function isSidePanelExists() {  
  const sidePanelContexts = await chrome.runtime.getContexts({  
    contextTypes: [chrome.runtime.ContextType.SIDE_PANEL],  
  });  
  return sidePanelContexts.length > 0;  
}

more details about this api, it can be found here. Let's refactor our code:

if (tab.url == NEW_TAB_DEFAULT_URL) {  
	if (await isSidePanelExists()) {  
	  await chrome.runtime.sendMessage({ message: CLOSE_SIDE_PANEL });  
	}
}

References

  1. runtime#method-getContexts
  2. sidePanel#method.
  3. runtime#method-connect
  4. Is there a way to detect if the chrome SidePanel is open