0
votes

I have a chrome extension with a popup page which passes a boolean variable to my content page via simple one-time requests. The content page would then do some action based on the status of the boolean variable passed from the popup page. This was working perfectly until I accidentally removed the extension (still in developer mode, the extension is unpacked) and had to re-load it.

This caused an extension context invalidated error to appear in the popup inspection console and the webpage console seems to validate that the popup page and content script are not communicating. The webpage with the chrome extension active shows this error: Unchecked runtime.lastError: The message port closed before a response was received.

Based on a few answers I've already seen, it seems that reloading my chrome extension has "orphaned" my original working content script from the rest of my extension, which causes the aforementioned "Unchecked runtime.lastError: The message port closed before a response was received." error on the webpage console.

I believe that I cannot just reinject my content script again as my content script has DOM event listeners. Is there a possible way to remove the currently running orphan script? Or is there any suggested workaround to this problem?

Here is my popup.js:

chrome.tabs.query({'active': true, 'currentWindow': true}, function (tabs) {
    chrome.tabs.sendMessage(tabs[0].id, {cTabSettings: (some boolean variable)});
});

Here is my content.js:

// Listening for message from popup.js
chrome.runtime.onMessage.addListener(
  function(request, sender, sendResponse) {
    if (request.cTabSettings === true) {
      enabled = true;
    } else if (request.cTabSettings === false) {
      enabled = false;
    }
});

// DOM listener and action
document.addEventListener('mousemove', function (e) {
   // Some action
   chrome.runtime.sendMessage({sender: "content", selText : "blah"}, function () {
      console.log("success");
   });
}, false);

I am using chrome developer mode version 76. Just to rephrase, this chrome extension was working (content script communicates with popup) before I accidentally reloaded it.

2

2 Answers

1
votes

Since the orphaned content script can still receive DOM messages, send one from your new working content script to the ghosted content script via window, for example. Upon receiving the message you'll unregister all listeners (and nullify any global variables) which will also make your old script eligible for automatic garbage collection.

content.js:

var orphanMessageId = chrome.runtime.id + 'orphanCheck';
window.dispatchEvent(new Event(orphanMessageId));
window.addEventListener(orphanMessageId, unregisterOrphan);

// register all listeners with named functions to preserve their object reference
chrome.runtime.onMessage.addListener(onMessage);
document.addEventListener('mousemove', onMouseMove);

// the popup script checks it to see if a usable instance of content script is running
window.running = true;

function unregisterOrphan() {
  if (chrome.i18n) {
    // someone tried to kick us out but we're not orphaned! 
    return;
  }
  window.removeEventListener(orphanMessageId, unregisterOrphan);
  document.removeEventListener('mousemove', onMouseMove);
  try {
    // 'try' is needed to avoid an exception being thrown in some cases 
    chrome.runtime.onMessage.removeListener(onMessage);
  } catch (e) {}
  return true;
});

function onMessage(msg, sender, sendResponse) {
  //...........
}

function onMouseMove(event) {
  // DOM events still fire in the orphaned content script after the extension
  // was disabled/removed and before it's re-enabled or re-installed
  if (unregisterOrphan()) { return }
  //...........
}

I'm assuming popup.html loads WebExtension browser API polyfill because it makes life much easier by allowing us to use async/await syntax instead of the mind-boggling callback hell.

<script src="browser-polyfill.min.js"></script>

popup.js should ensure a content script is injected before sending a message:

async function sendMessage(data) {
  const [tab] = await browser.tabs.query({active: true, currentWindow: true});
  if (await ensureContentScript(tab.id)) {
    return await browser.tabs.sendMessage(tab.id, data);
  }
}

async function ensureContentScript(tabId) {
  try {
    const [running] = await browser.tabs.executeScript(tabId, {
      code: 'window.running === true',
    });
    if (!running) {
      await browser.tabs.executeScript(tabId, {file: 'content.js'});
    }
    return true;
  } catch (e) {}
}
0
votes

Please check this answer

It's not about removal of script but to avoiding error in case.