Search code examples
google-chrome-extension

"Error: `sidePanel.open()` may only be called in response to a user gesture." Received this when open side panel with keyboard shortcut


I want to use keyboard shortcut to open side panel but I got an error: Uncaught (in promise) Error: sidePanel.open() may only be called in response to a user gesture. error image

{
  "manifest_version": 3,
  "name": "Global side panel",
  "version": "1.0",
  "description": "Sidepanel declared only in the manifest visible to all sites",
  "icons": {
    "16": "images/icon-16.png",
    "48": "images/icon-48.png",
    "128": "images/icon-128.png"
  },
  "background": {
    "service_worker": "background.js"
  },
  "side_panel": {
    "default_path": "sidepanel.html"
  },
  "permissions": ["sidePanel"],
  "commands": {
    "Open-side-panel": {
      "suggested_key": {
        "default": "Ctrl+Shift+Y",
        "mac": "Command+Shift+Y"
      },
      "description": "Open side panel"
    }
  }
}
chrome.commands.onCommand.addListener(async (command) => {
  console.log(`Command "${command}" called`);
  const [tab] = await chrome.tabs.query({active: true})
  chrome.sidePanel.open({tabId:tab.id});
});

This is the repository


Solution

  • It's a bug: this API doesn't keep the gesture flag for more than ~1ms, unlike the other APIs that do it for a short amount of time up to 1 second.

    The workaround in a simple case like this is using a callback instead of await:

    chrome.commands.onCommand.addListener(() => {
      chrome.tabs.query({ active: true, currentWindow: true }, ([tab]) => {
        chrome.sidePanel.open({ tabId: tab.id });
      });
    });
    

    The workaround for a more complicated asynchronous query is to prepare tabId beforehand and make the worker script run forever in order to keep the variable:

    let activeTabId;
    // keep alive, see stackoverflow.com/a/66618269
    setInterval(chrome.runtime.getPlatformInfo, 25e3);
    chrome.runtime.onStartup.addListener(async () => {
      activeTabId = (await chrome.tabs.query({ active: true, currentWindow: true }))[0]?.id;
    });
    chrome.tabs.onActivated.addListener(info => {
      activeTabId = info.tabId;
    });
    chrome.commands.onCommand.addListener(() => {
      chrome.sidePanel.open({tabId});
    });