How do I fix the following issues with my port of a manifest v2 browser extension to manifest v3?

54 Views Asked by At

I am in the process of transitioning a manifest v2 Chrome extension to manifest v3 and while converting my background script background.js to a service worker, and I am getting error messages in Google Chrome. I am somewhat familiar with Manifest v3, however the concept of service workers is still very confusing to me. Here is the error message. Note: Keep in mind, despite the single error message, I believe there may be some logical errors rather than syntax errors in my source code that may present undesired or unexpected behavior in the browser extension.

Uncaught (in promise) Error: Could not establish connection. Receiving end does not exist.

To put this error into context, I have included the code of my service worker called service-worker.js, content-script.js, and manifest.json. I think I did most of the "heavy lifting" porting my MV2 extension to MV3, but there seems to be some minor issues.

service-worker.js

"use strict";

function sendAudioMessage(actionType, audioFile) {
  chrome.tabs.query({ active: true, currentWindow: true }, function(tabs) {
    tabs.forEach(tab => {
      // Check if the tab's URL starts with 'chrome://'
      if (tab.url.startsWith('chrome://')) {
        return; // Skip to the next tab
      }

      // Send the audio message
      chrome.tabs.sendMessage(tab.id, { action: actionType });
    });
  });
}

chrome.webNavigation.onCreatedNavigationTarget.addListener(onNav);
chrome.webNavigation.onBeforeNavigate.addListener(onNav);
chrome.webNavigation.onReferenceFragmentUpdated.addListener(onNav);
chrome.webNavigation.onHistoryStateUpdated.addListener(onNav);

function onNav({frameId}) {
  console.log(frameId);
  if(frameId > 0) return;
  sendAudioMessage('playNavigationSound');
}

chrome.downloads.onChanged.addListener(delta => {
  if(delta.state && delta.state.current === "complete") {
    sendAudioMessage('playDownloadCompleteSound');
  }
  if(delta.error && delta.error.current) {
    sendAudioMessage('playDownloadErrorSound');
  }
});

chrome.tabs.onUpdated.addListener((tabId, {mutedInfo}) => {
  if(mutedInfo && mutedInfo.reason === "user") {
    sendAudioMessage('playMuteWarningSound'); 
  }
});

function changeVolume(volumeLevel) {
  chrome.tabs.query({ active: true, currentWindow: true }, function(tabs) {
    tabs.forEach(tab => {
      // Check if the tab's URL starts with 'chrome://'
      if (tab.url.startsWith('chrome://')) {
        return;  // Skip to the next tab
      }

      // Your original code to send the volume change message
      chrome.tabs.sendMessage(tab.id, { action: 'changeVolume', volume: volumeLevel });
    });
  });
}

// *** Message Handler ***
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
  console.log(message, sender, sendResponse);
  switch (message.action) {
    case 'playNavigationSound':
      sendAudioMessage('playNavigationSound');
      break;
    case 'playDownloadCompleteSound':
      sendAudioMessage('playDownloadCompleteSound');
      break;
    case 'playDownloadErrorSound':
      sendAudioMessage('playDownloadErrorSound');
      break;
    case 'playMuteWarningSound':
      sendAudioMessage('playMuteWarningSound');
      break;
    case 'changeVolume':
      changeVolume(0.75); 
      break;
    default:
      console.log('Unknown message action:', message.action);
  }
});

content-script.js

// Helper function to play audio
function playAudio(audioFile) {
    console.log(audioFile, chrome.runtime.getURL(audioFile));
    const audio = new Audio(chrome.runtime.getURL(audioFile)); // Use chrome.runtime.getURL for packaged extension resources
    // Play the audio only when the EventListener for click is triggered
    audio.addEventListener('click', () => {
        audio.play();
    });
  }
  
  // Helper function for setting volume
  function setAudioVolume(volumeLevel) {
    const audioElements = document.querySelectorAll('audio');
    audioElements.forEach(audio => {
        audio.volume = volumeLevel;
    });
  }
  
  // Receive messages from your service worker
  chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
    console.log(message, sender, sendResponse);
    switch (message.action) {
        case 'playNavigationSound':
            playAudio('nav.ogg');
            break;
        case 'playDownloadCompleteSound':
            playAudio('complete.ogg');
            break;
        case 'playDownloadErrorSound':
            playAudio('error.ogg');
            break;
        case 'playMuteWarningSound':
            playAudio('unlock.ogg');
            break;
        case 'changeVolme':
            setAudioVolume(0.75);
            break;
        default:
            console.log('Unknown message action:', message.action);
    }
  });  

manifest.json

{
  "manifest_version": 3,
  "name": "PopupSound",
  "description": "Plays a click sound when you click on a link. Also plays a trumpet sound when you finish downloading a file.",
  "author": "Michael Gunter",
  "version": "3.0",
  "default_locale": "en",
  "offline_enabled": true,
  "icons": {
    "32": "icon_32.png",
    "96": "icon_96.png",
    "128": "icon_128.png"
  },
  "background": {
    "service_worker": "service-worker.js"
  },
  "content_scripts": [
    {
      "matches": ["<all_urls>"], 
      "js": ["content-script.js"],
      "run_at": "document_start"
    }
  ],
  "permissions": [
    "webNavigation",
    "downloads",
    "tabs"
  ],
  "web_accessible_resources": [
    {
    "resources": ["*.ogg"],
    "matches": ["<all_urls>"],
    "extensions": []
    }
    ],
  "content_security_policy": {}
}

Edit: While I no longer get the error message when the extension runs, I now get two error messages when audio is expected to be played from the browser extension.

Uncaught (in promise) Error: Could not establish connection. Receiving end does not exist.

Uncaught (in promise) NotAllowedError: play() can only be initiated by a user gesture.

I hope this helps.

1

There are 1 best solutions below

7
guest271314 On

There's no message handler in the ServiceWorker for the messages sent from the content script.

See Message passing at

To receive the message, set up a runtime.onMessage event listener. These use the same code in both extensions and content scripts:

content-script.js or service-worker.js:

chrome.runtime.onMessage.addListener(
  function(request, sender, sendResponse) {
    console.log(sender.tab ?
                "from a content script:" + sender.tab.url :
                "from the extension");
    if (request.greeting === "hello")
      sendResponse({farewell: "goodbye"});
  }
);