Search code examples
google-chrome-extensionpermissionsbackgroundservice-worker

Can a Chrome Extension with Manifest V3 modify tabs in the background?


I am trying to get into chrome extension development and want to make an extension which highlights a specific word on every page from a background script.
If I give my extension the activeTab permission, it is listed as "Access requested" and I need to invoke it by opening the popup, then after reloading the page it will highlight the word.

Access requested

The documentation says "essentially, activeTab grants the tabs permission temporarily", so I switched to giving the tabs permission because I don't want to open the popup every time I visit a new website. However, now the extension is listed as "no access needed" and the highlighting does not work regardless of whether I invoke the popup or not.

No access needed

Here is my service-worker background.js:

chrome.tabs.onUpdated.addListener(function(tabId, changeInfo, tab){
  if (changeInfo.status == 'complete') {  
    chrome.scripting.executeScript({
      target: { tabId: tabId },
      function: highlightwords,
    });
  }
})


function highlightwords(){
  var list = document.getElementsByTagName("p")
  var search_words = ["the", "it", "you"]
  var words = []

  for(var i = 0; i < list.length; i++){
    var words = list[i].textContent.split(' ')
    for (var j = 0; j < words.length; j++) {
      if (search_words.includes(words[j])) {
        console.log(words[j]);
        var elem = document.createElement("span")
        elem.textContent = words[j]
        elem.style.color = "red"
        words[j]=elem.outerHTML
      }      
    }
    
    list[i].innerHTML = words.join(' ')
  }
}

manifest.json:

{
  "name": "Getting Started Example",
  "description": "Build an Extension!",
  "version": "1.0",
  "manifest_version": 3,
  "background": {
      "service_worker": "background.js"
  },
  "action": {
    "default_popup": "popup.html",
    "default_icon": {
      "16": "/images/get_started16.png",
      "32": "/images/get_started32.png",
      "48": "/images/get_started48.png",
      "128": "/images/get_started128.png"
    }
  },
  "icons": {
    "16": "/images/get_started16.png",
    "32": "/images/get_started32.png",
    "48": "/images/get_started48.png",
    "128": "/images/get_started128.png"
  },
  "options_page": "options.html",
  "permissions": ["storage", "scripting", "tabs"]
}

I'd be very grateful for any hint on what's going wrong here or which alternatives there are (apart from using manifest v2, which probably doesn't have much of a future, at least in Chrome).


Solution

  • The only purpose of the tabs permission is to show url in changeInfo and tab objects e.g. in chrome.tabs.onUpdated event. You don't use it and you don't need it for this task.

    1. Remove tabs and scripting permissions, remove the background worker.

    2. Declare a content script in manifest.json

      "content_scripts": [{
        "matches": ["*://*/*"],
        "js": ["content.js"]
      }]
      
    3. content.js will run in all http/https sites (this is what the first * in *://*/* means) and highlight the text.

    P.S. Note that your current code destroys event listeners added by the page to any nested elements inside p because you assign to innerHTML. This is bad. A much better approach is to replace the matching parts of text inline by using DOM methods createTreeWalker and splitText, example, or just use mark.js.