Execute a content script before popup has been clicked

My extension is nearly ready to be submitted, but I can’t work out how to re-factor a part of the code that I hacked together in order for it to work as expected.

My extension is a popup that displays YouTube playlists that are saved in local storage, and the user can either click a button on the UI to add the current video to the list or they can right click on a video to add it.

I’m trying to run my content script from the background script so that it loads immediately, otherwise when I send a message from a context menu click I get the error that no receiving end exists. The hack I used was so load the content script within the context menu onClick event listener after a short delay, but no matter where else in the background script I attempt to run tabs.executeScript I always get an error of some sort.

Ideally I don’t want the user to have to open the extension before they have right-click functionality, so what’s the best way to proceed?

This is the entirety of background.js as the bulk is contained in the popup script and the content script:

function onCreated() {
    if (browser.runtime.lastError) {
        console.log(`Error: ${browser.runtime.lastError}`);
    } else {
        console.log("Context menu item created successfully");
    }

    // EXECUTES BUT CAN'T MAKE A CONNECTION
    // browser.tabs.executeScript({file: "/content_scripts/content.js"})
}

browser.contextMenus.create({
    id: "add",
    title: "Add To Playlist",
    contexts: ["all"],
    documentUrlPatterns: ["*://www.youtube.com/*"]
}, onCreated);

// ERROR: MISSING HOST PERMISSION FOR THE TAB
// browser.tabs.executeScript({file: "/content_scripts/content.js"})

browser.contextMenus.onClicked.addListener((info) => {

    // WORKS HERE BUT ONLY WITH TIMEOUT SET
    browser.tabs.executeScript({file: "/content_scripts/content.js"})
    ///////////////////////////////////////

    if(info.menuItemId === "add"){
        if(info.linkUrl === undefined){
            setTimeout(()=>{
                browser.tabs.query({active: true, currentWindow: true})
                .then(response =>{
                    browser.tabs.sendMessage(response[0].id, {
                        command: "add url",
                        url: info.pageUrl
                    })
                    .then(response => console.log(response))
                    .catch(error => console.log(error))
                })
            }, 100)
        }else{
            setTimeout(()=>{
                browser.tabs.query({active: true, currentWindow: true})
                .then(response =>{
                    browser.tabs.sendMessage(response[0].id, {
                        command: "add url",
                        url: info.linkUrl
                    })
                    .then(response => console.log(response))
                    .catch(error => console.log(error))
                })

            }, 100)
        }
        return;
    }
})

You can’t inject script into page without a host permission for that domain.
If you have a “activeTab” permission, you can do that if the user interacted with your addon somehow, like opening a popup window or clicking a context menu item.

If you want to inject it into YouTube every time a user visits it, you want to use a “content_scripts” key in the manifest file:

However, this will require “youtube.com” host permission so if don’t have it yet, adding it will warn every single user of your addon that you want another permission. And people don’t like that at all.

More info:

Thanks for the quick reply. This is what I already have as part of my manifest file:

"content_scripts": [
      {
        "matches": [
            "*://www.youtube.com/*"],
          "exclude_matches": [
            "*://www.youtube.com/embed/*",
            "*://www.youtube.com/pop-up-player/*"
          ],
        "js": ["content.js"]
      }
    ],

    "permissions": ["contextMenus", "tabs", "activeTab", "clipboardWrite"],
    "background": {
      "scripts": ["background.js"]
    }

The problem isn’t getting the script to run, it’s getting it to run without the hack in my example.

Is what I want to achieve even possible?

Quick tip, when pasting a code here, place triple backtick ( ```) on the line beofre and after the code. That will enable proper formatting.

So from your manifest I would say that “content.js” is injected into all youtube pages, unless they don’t match those excluded matches - are you sure about those?

Content scripts are automatically injected into pages they match, so no hacks are needed. See the docs I’ve mentioned, is says it in the first sentence:

Instructs the browser to load content scripts into web pages whose URL matches a given pattern.

1 Like

Ok, so I worked out that I didn’t need to manually inject the script as well as having the matches array in my manifest - the reason I was using the tabs.executeScript method was because my project was originally a fork of the Beastify extension example.
Then I worked out that the path for my content script needed to be changed, and I could then delete other unnecessary code that was left over from the example project.

It makes much more sense to me now, and runs as I expected it to. Thanks for your help.

2 Likes