Why am I getting an error when I click chrome extension?

I am trying to create a simple chrome extension. I was able to load the extension but when I clicked the extension icon next to the address bar I get an error : Unchecked runtime.lastError: Could not establish connection. Receiving end does not exist. Why?

My manifest.json file :

{
    "name" : "Test",
    "version" : "0.1",
    "permissions" : ["<all_urls>", "tabs", "activeTab"],
    "content_scripts" : [
        {
            "matches" : ["<all_urls>"],
            "js" : ["content.js"]
        }
    ],
    "background" : {
        "scripts" : ["background.js"]
    },
    "browser_action" : {
        "default_popup" : "popup.html"
    },
    "manifest_version" : 2
}

popup.html :

<html>
    <head>
        <link rel="stylesheet" href="popup.css">
        <script src="popup.js"></script>
    </head>
    <body>
        <button>Click</button>
    </body>
</html>

popup.js

chrome.tabs.query({active : true, currentWindow : true}, (tabs)=>{
    chrome.tabs.sendMessage(tabs[0].id, {action : "button_click"}, response =>{
        console.log(response);
    })
});

content.js

chrome.runtime.onMessage.addListener((request, sender, sendResponse)=>{
    if(request.action === "button_click"){
        console.log("You clicked the button");
        sendResponse("success");
    }
})

popup.js is working fine because it is logging the response but content.js returns undefined response. background.js is still empty so I excluded it. This is my first time diving into chrome APIs so I’m really confused as to why it isn’t working.

Answer

Content scripts run only when a page loads for the first time in a tab so when you install or reload your extension on chrome://extensions page you also need to reload the tabs or reinject the script explicitly. Also, by default content scripts run after DOMContentLoaded so it may be still pending injection if the page is still loading (you can tell it to run at document_start).

Note that in your use case it’s possible you don’t need an automatically injected content script so you can remove content_scripts section, remove <all_urls> from permissions, and switch to programmatic injection which can pass data even without messaging.