-1

I am developing my first chrome extension, I want to have a toggle button on my popup which allows users to enable or disable the extension for that site, though not completely disable the extension but technically control the execution of the content.js for that particular domain.

Something similar to

enter image description here

popup.js

const toggleBtn = document.querySelector(".toggle");

toggleBtn.addEventListener("click", function(){
    if(toggleBtn.checked === true){
        console.log('inject content.js');
    }else{
        console.log('remove content.js');
    }
});

I can see there is a way to inject it but there is no way to remove it.

          chrome.scripting.executeScript({
                target: {tabId},
                files: ['content.js'],
            });

Note, I don't want to reload the page but want to disable the content.js. How can I achieve this functionality inside my chrome extension. I see there are many extensions doing this like Adblocker and Wordtune.

I went through few SO threads like this one but most of them are for Manifest V2 and few packages that are recommended are obsolate now. I also could not find a complete example anywhere.

Todo

  1. I should be able to turn the extension on/off for the current domain from within the Popup.
  2. It should store the state inside a chrome.storage.sync so that next time I installed the extension I should not again turn on/off for the selected websites.
  3. As soon as I toggle the button, the changes should be visible if I have opened the same domain in another tab.
  4. The extension's icon should be changed in real time as well e.g grey icon for disabled status
  5. If I close and open a website, the icon and the toggle button should be restored their on/off status and update the icon accordingly.

My Attempt (Solves @toDo 1,2,3,4,5)

background.js

const icons = {
    enabled: {
        '16': 'icon-16.png',
        '19': 'icon-19.png',
        '38': 'icon-38.png',
        '48': 'icon-48.png',
        '128': 'icon-128.png'
    },
    disabled: {
        '16': 'icon-16-off.png',
        '19': 'icon-19-off.png',
        '38': 'icon-38-off.png',
        '48': 'icon-48-off.png',
        '128': 'icon-128-off.png'
    }
};

const getExtensionStatus = host => new Promise((resolve, reject) => {
    chrome.storage.sync.get(host, result => {
        if (result[host] !== undefined) {
            resolve(result[host]);
        } else {
            setExtensionStatus(host, true);
        }
    });
});

const setExtensionStatus = (host, toggleBtnStatus) => {
    const data = { [host]: toggleBtnStatus };
    chrome.storage.sync.set(data, () => {});
};


chrome.runtime.onInstalled.addListener((details) => {
    if (details.reason === "install") {
        chrome.action.setIcon({ path: icons.enabled });
    }
});

const init = async tab => {
    const url = new URL(tab.url);
    if (url.protocol !== "chrome-extension:") {
        const host = url.host;
        const status = await getExtensionStatus(host);
        const icon = status ? icons.enabled : icons.disabled;
        chrome.action.setIcon({ path: icon });
    }
};

chrome.tabs.onUpdated.addListener((tabId, changeInfo, tab) => {
    if (changeInfo.status === "complete") init(tab);
});

chrome.tabs.onActivated.addListener(info => {
    chrome.tabs.get(info.tabId, init);
});

chrome.runtime.onMessage.addListener(async (request, sender, sendResponse) => {
    if (request.type === "getExtensionStatus") {
        const status = await getExtensionStatus(request.host);
        sendResponse({ status });
        return true;
    } else if (request.type === "setExtensionStatus") {
        setExtensionStatus(request.host, request.status);
        const icon = request.status ? icons.enabled : icons.disabled;
        chrome.action.setIcon({ path: icon });
    }
});

popup.js

document.addEventListener("DOMContentLoaded", function () {
    const toggleBtn = document.querySelector(".toggle");

    chrome.tabs.query({ active: true, currentWindow: true }, (tabs) => {
        const host = new URL(tabs[0].url).host;

        chrome.runtime.sendMessage({ type: "getExtensionStatus", host }, (response) => {
            toggleBtn.checked = response.status;
        });

        toggleBtn.addEventListener("click", () => {
            chrome.runtime.sendMessage({ type: "setExtensionStatus", host, status: toggleBtn.checked });
        });
    });
});

What left?

The content.js is not available after some time of inactiveness of the tab. How can I inject the script again and avoid running event multiple times?

content.js

document.addEventListener("keydown", listenEnterkey);

chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
    if (request.type === "setExtensionStatus") {
        console.log(request.status);
        if (request.status) {
            // Add event listener here
            document.addEventListener("keydown", listenEnterkey);
        } else {
            // Remove event listener here
            document.removeEventListener("keydown", listenEnterkey);
        }
    }
});
SkyRar
  • 1,107
  • 1
  • 20
  • 37
  • Please use [unregisterContentScripts](https://developer.chrome.com/docs/extensions/reference/scripting/#method-unregisterContentScripts). – Norio Yamamoto Feb 04 '23 at 22:27
  • @NorioYamamoto On the docs[https://developer.chrome.com/docs/extensions/reference/scripting/#unregister-all-dynamic-content-scripts], it says "Unregistering content scripts will not remove scripts or styles that have already been injected." – SkyRar Feb 04 '23 at 22:32
  • Excuse me, I misunderstood. – Norio Yamamoto Feb 04 '23 at 23:54
  • There's no way to remove a script that already ran, JavaScript doesn't support this. You can send a message to it so that your listener in the injected script will disable its effects. – wOxxOm Feb 05 '23 at 11:35
  • @wOxxOm I would really appreciate your effect if you could show a complete example. I am struggling to find a proper example since 2-3 days. I hope we need a background.js also. So that it can turn off the effect domain wise. – SkyRar Feb 05 '23 at 12:34
  • See [this example](https://stackoverflow.com/a/75283704). – wOxxOm Feb 05 '23 at 14:37
  • @wOxxOm Thank you, the code helped me anyhow. Please have a look on my attempt. Updated the question. – SkyRar Feb 06 '23 at 04:16

1 Answers1

1

For example, if you add a button with a script, I don't think the added button will disappear even if you remove the script.

Therefore, the ON/OFF function is built into the script, and it is operated by the message.

manifest.json

{
  "name": "content_scripts + popup",
  "version": "1.0",
  "manifest_version": 3,
  "content_scripts": [
    {
      "js": [
        "matches.js"
      ],
      "matches": [
        "<all_urls>"
      ]
    }
  ],
  "host_permissions": [
    "<all_urls>"
  ],
  "action": {
    "default_popup": "popup.html"
  }
}

matches.js

console.log("matches.js");

let isEnabled = true;

console.log(isEnabled);

chrome.runtime.onMessage.addListener((message) => {
  switch (message) {
    case "on":
      isEnabled = true;
      break;
    default:
      isEnabled = false;
      break;
  }

  console.log(isEnabled);
});

popup.js

const send = (s) => {
  chrome.tabs.query({ active: true, currentWindow: true }, (tabs) => {
    chrome.tabs.sendMessage(tabs[0].id, s);
  });
}

document.getElementById("on").onclick = () => {
  send("on");
}

document.getElementById("off").onclick = () => {
  send("off");
}

popup.html

<html>

<body>
  <button id="on">on</button>
  <button id="off">off</button>
  <script src="popup.js"></script>
</body>

</html>
Norio Yamamoto
  • 1,495
  • 2
  • 3
  • 10
  • Thank you. Your code helped me understand the concept. I attempted and waiting for your inputs. I have updated my question now, pls look. – SkyRar Feb 06 '23 at 04:18