Switching focus from the sidebar to the active tab

I’m adding a sidebar to my Firefox extension which will let users insert text into a web page textbox by clicking on an element, or pressing ENTER on a selection, in a list that is displayed in the sidebar.

But for this to work, the focus would need to switch from the extension sidebar to the active tab in the same browser window. This is because the text insertion is performed by the content script, which will locate the active element in the document, which should be the text box that the user has focused prior to interacting with the extension sidebar.

I read through the sidebarAction API documentation and couldn’t find anything that would cause this behaviour to happen. Any ideas?

When you navigate away from the tab, the focused field is blurred. When you come back, the field is refocused. You can simulate this by adding an onblur and onfocus event to a form field.

My guess is that the content script fires before the focus event when the tab is selected again. This is of course assuming that you return focus back to the original tab and that the focus does not stay on the sidebar.

A hacky workaround would be to have a content script that stores the focused element. You may even be able to do that only when focus is moved away from the tab.

Isn’t the focused element preserved in each document?
For example, even while typing this reply, if I press “Ctrl + H” to open History sidebar (which focuses the History search input) and then I press “F12” and execute document.activeElement, it will still return this textarea where I type this.

So the content script running in a tab should still see whatever was last focused.
Or maybe I misunderstood the question, it’s still early morning :slight_smile:.

1 Like

Thanks everyone for the hints! I made it work by sending a message to the content script from the sidebar to focus the window. The content script calls activeElement.ownerDocument.defaultView.focus() to focus the window (just calling window.focus() doesn’t seem to do anything), and then focuses the active element.

BTW, the reason why I need to focus the window is because the content script is injected into all frames ("all_frames": true in the extension manifest). If there are multiple web pages displayed in a <frame> or <iframe>, the text insertion into a web page textbox should only occur on the frame that has the focus.

To be honest, I’m surprised that worked for you, @aecreations. In my testing document.activeElement.ownerDocument.defaultView.focus() and window.focus() shared the same (odd) behavior.

As I type this, I have a test extension installed in the sidebar. If I click into the sidebar, then click “Focus main window”, my cursor will appear back in the reply textarea on this site. If I immediately repeat that process, it doesn’t switch back. There seems to be some minimum amount of interaction with the page that is required in order for focus to switch back. Its really quite odd.

manifest.json

{
  "name": "Sidebar - focus main window",
  "version": "1.0",
  "manifest_version": 3,
  "sidebar_action": {
    "default_panel": "sidebar.html"
  },
  "content_scripts": [{
    "matches": ["<all_urls>"],
    "js": ["content.js"]
  }]
}

sidebar.html

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <script defer src="sidebar.js"></script>
</head>
<body>
  <input type="text" id="text-input"></input>
  <button id="focus-main">Focus main window</button>
</body>
</html>

sidebar.js

const button = document.getElementById("focus-main");

button.addEventListener("click", async () => {
  const [tab] = await browser.tabs.query({active: true, lastFocusedWindow: true});
  browser.tabs.sendMessage(tab.id, "focus-main");
});

content.js

browser.runtime.onMessage.addListener((message) => {
  if (message === "focus-main") {
    console.log("focus-main message received");
    // document.activeElement.ownerDocument.defaultView.focus();
    window.focus();
    console.log("doc has focus?", document.hasFocus())
    setTimeout(() => console.log("doc has focus?", document.hasFocus()), 100)
  }
});

Having looked into this further, I’m now seeing the same results, which would explain my initial observation that window.focus() had not worked. Quite odd indeed, and not a great user experience.