How to open Chrome Extension's side panel clicking a button inside the popup?
Asked Answered
P

2

7

I have a chrome extension and want to use the recently added chrome.sidePanel API to open the side panel and show my chrome extension there when a user click a button inside the extension's popup.

I have tried the following code:

App.tsx

const handleOpenSidePanel = async () => {
  chrome.tabs.query({active: true, currentWindow: true}, function(tabs) {
    const currentTabId = tabs[0].id;
    chrome.runtime.sendMessage({ type: 'openSidePanel', tabId: currentTabId });
  });
}

background.js

chrome.runtime.onMessage.addListener((message, tab) => {
  if (message.type === "openSidePanel") {
    chrome.sidePanel.open({ windowId: tab.windowId});
  }
});

manifest.json

"permissions": [
  "sidePanel",
  "tabs"
],
"side_panel": {
  "default_path": "index.html"
}
...

When I click on the button, I get the following error and the side panel doesn't open:

Uncaught (in promise) Error: At least one of "tabId" and "windowId" must be provided

Pantia answered 13/3 at 4:23 Comment(6)
1) message, tab should be message, {tab}, but it won't work anyway because the popup doesn't have a tab, it's a special window not related to the tab. 2) There's no need for messages and the background script here as you can directly call chrome.sidePanel.open in the popup app.Odeen
Calling chrome.sidePanel?.open() directly in the popup file (App.tsx) returns Property 'sidepanel' does not exist on type 'typeof chrome'.ts(2339)Pantia
It's just a linter error. Simply use a better chrome types package e.g. the official one.Odeen
@wOxxOm chrome.sidePanel is not supposed to be called in the application file level, but on the background/service worker. Read developer.chrome.com/docs/extensions/reference/api/sidePanel. There are no examples of the API being called at the application level.Pantia
No, all chrome API can be called by any extension page that has chrome-extension:// URL, which means the action popup can do it too. You can suggest to add this info to all documentation pages because looking back indeed quite a few people share the same misconception. BTW there's no such thing as "application file level" in chrome extensions.Odeen
I also get Property 'sidepanel' does not exist on type 'typeof chrome' but I'm getting it in the service-worker file.Mun
P
1

Solved this problem by using the following:

const handleOpenSidePanel = () => {
  chrome.windows.getCurrent({ populate: true }, (window) => {
    (chrome as any).sidePanel.open({ windowId: window.id });
  });
}
Pantia answered 20/6 at 1:27 Comment(0)
R
7

You have two options,

  1. Open the sidepanel when the icon in the action bar is clicked (You don't not need a popup in this case)

add to service-worker.js:

chrome.sidePanel
          .setPanelBehavior({ openPanelOnActionClick: true })
          .catch((error) => console.error(error));
  1. Open the side panel on user interaction (clicking on a button) from the popup window

In this case, your manifest version 3 has these properties among the other:

Add to manifest.json:

  "background": {
    "service_worker": "service-worker.js"
  },
  "side_panel": {
    "default_path": "sidepanel-global.html"
  },
  "action": {
    "default_popup": "popup.html"
  },
  "permissions": ["sidePanel"],

Then you have a button (with id="openSidePanel") on the popup, which will send a message to the service worker when clicked.

Add to popup.js:

document.getElementById('openSidePanel').addEventListener('click', function() {
        chrome.runtime.sendMessage({action: 'open_side_panel'});
    });

Service worker will receive the message, but before that, you need to make sure to pass the windows tab information to the service worker:

Add to service-worker.js:

// to find the windowId of the active tab
let windowId;
chrome.tabs.onActivated.addListener(function (activeInfo) {
  windowId = activeInfo.windowId;
});

// to receive messages from popup script
chrome.runtime.onMessage.addListener((message, sender) => {
  (async () => {
    if (message.action === 'open_side_panel') {
      chrome.sidePanel.open({ windowId: windowId });
    }
  })();
});

Here you can find more options: Chrome Side Panels, Why the sendMessage deosn't pass tabId

Regenaregency answered 23/5 at 11:41 Comment(3)
Best answer to the question thank you.Obliquity
If your button is injected inside a web page in a tab, there's no need to do ...onActivated.addListener. Instead you can simply use sender object like this: ...sidePanel.open({ tabId: sender.tab.id, windowId: sender.tab.windowId }). I think this works better in my experience as it will always be the correct set of IDs. However, note that this only works if the message is sent from a web page in a tab (not from other places like the chrome extension popup).Margalit
BTW, it doesn't look like this would work with the async wrapper because you get this security blockage in Chrome: Error: sidePanel.open() may only be called in response to a user gesture. So it has to be synchronous and directly in response to the button click, not asynchronous. The async wrapper should be removed. (Not sure how it worked for you with it included. Maybe it works in popup, but not in content scripts.)Margalit
P
1

Solved this problem by using the following:

const handleOpenSidePanel = () => {
  chrome.windows.getCurrent({ populate: true }, (window) => {
    (chrome as any).sidePanel.open({ windowId: window.id });
  });
}
Pantia answered 20/6 at 1:27 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.