Issues with tabs[0].id in onMessage callback - javascript

I'm writing an extension to demonstrate the use of messages. Here's the idea.
There is an HTML page with a mytext text field and a popup page that also contains a mytext field.
Both text fields are always synchronized in content.
Direct messages between the popup page and the content page do not work. Therefore, all communication takes place through an intermediary - the background page.
Messages are passed between the popup page and the background page via port.postMessage. The background page and content page communicate via chrome.runtime.sendMessage and chrome.tabs.sendMessage.
background.js
let popup_port;
debugger;
chrome.extension.onConnect.addListener(function (port)
{
popup_port = port;
popup_port.onMessage.addListener(function (msg)
{
chrome.tabs.query(
{
currentWindow: true,
active: true
},
function (tabs)
{
// This is where the error occurs.
chrome.tabs.sendMessage(tabs [0].id, msg);
});
return true;
});
});
chrome.runtime.onMessage.addListener(function (msg)
{
popup_port.postMessage(msg);
});
content.js
debugger;
chrome.runtime.onMessage.addListener(function (msg)
{
if (msg.query)
chrome.runtime.sendMessage(mytext.value);
else
mytext.value = msg.input;
});
popup.js
let port = chrome.extension.connect({name: '...'});
debugger;
port.postMessage({query: true});
port.onMessage.addListener(function (textMsg)
{
mytext.value = textMsg;
});
function postMyMessage ()
{
port.postMessage({input: mytext.value});
}
mytext.oninput = postMyMessage;
When I open a popup window, everything works correctly - the text from the main page also appears in the field of the popup page.
But when I enter text into the field from the popup page, an error occurs in the background page:
TypeError: Cannot read property 'id' of undefined
When I tested my code while writing this post, it (suddenly!!!) worked correctly - when I enter text in the popup, it appears in the main page as well. What was it !?

You say that it is possible to directly establish a connection between the popup page and the content page. How to do it right?
I placed this in the popup.js file:
let port = chrome.extension.connect({name: 'abc'});
debugger;
port.postMessage('hello');
while in content.js:
chrome.extension.onConnect.addListener(function (port)
{
port.onMessage.addListener(function (msg)
{
alert(msg);
});
});
As a result an error occurs:
Could not establish connection. Receiving end does not exist.
What is the correct way to organize two-way communication between popup.js and other parts of the extension (content.js)?

Related

Why is my message being sent from Chrome page action popup not being received by content script (Chrome Extension)?

I am trying to send a message from my popup to my main content script which will change some values that I have added to a page using the method updateInfo(val). However, I am running into an issue where my message is either not being sent at all or just not being received by content.js. I tried using chrome.runtime.sendMessage(), but that didn't work, so I have went overkill and am sending a message to every tab. My content.js is still not receiving the message. I have confirmed this in the chrome debugger. When I change the select box which lives in the popup, it hits changeYearInfo(), but then the message is never received in content.js.
My content.js:
chrome.runtime.onMessage.addListener(
function (request, sender, sendResponse) {
if (request.message === "Year Value Changed") {
let newVal = request.value;
updateInfo(newVal);
}
});
My popup.js:
function changeYearInfo() {
let newYearVal = document.getElementById('yearSessions').value;
chrome.tabs.query({}, tabs => {
tabs.forEach(tab => {
chrome.tabs.sendMessage(tab.id, {
"message": "Year Value Changed",
"value": newYearVal
});
});
});
}
document.addEventListener('DOMContentLoaded', function () {
document.getElementById('yearSessions').addEventListener("change", changeYearInfo);
}
);
How would I fix this issue? I've successfully used message passing multiple times from background.js to content.js and vice versa, but this is the first time I've tried sending a message from popup to content.
EDIT: This has been solved. The issue was not in the message passing, but that I was basing my conclusion off of the debugger tool, and I was incorrectly using the popup debugger when trying to check if my message was being received in content.js

Chrome extension: How to remove orphaned script after chrom extension update

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.
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.runtime.id) {
// 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 }
//...........
}
popup.js should ensure a content script is injected before sending a message:
async function sendMessage(data) {
const [tab] = await chrome.tabs.query({active: true, currentWindow: true});
if (await ensureContentScript(tab.id)) {
return await chrome.tabs.sendMessage(tab.id, data);
}
}
async function ensureContentScript(tabId) {
try {
const [{result}] = await chrome.scripting.executeScript({
target: {tabId},
func: () => window.running === true,
});
if (!result) {
await chrome.scripting.executeScript({
target: {tabId},
files: ['content.js'],
});
}
return true;
} catch (e) {}
}
Please check this answer
It's not about removal of script but to avoiding error in case.

Receive postMessage from chrome new tab extension

I have a popup (that is not made by me) that sends a postMessage for a login callback.
In the new tab page (that opened the popup), I am unable to receive this message.
Here is my code:
window.addEventListener("message", receiveMessage, false);
function receiveMessage(event)
{
console.log("MESSAGE RECEIVED!");
console.log(event.data);
}
^This does not work.
(I am aware of the following API, chrome.runtime.onMessageExternal , but since the popup does not send a message via the chrome runtime, I cannot use this)
How do I solve this problem?
Post message is a back and forth deal. Try this.
/* in the extension */
var description = {};
chrome.tabs.sendMessage(tabs[0].id, {
desc: 'some value'
}, function(response) {
description.value = response.details;
/* do your thing */
});
/* in the content script (your popup) */
chrome.runtime.onMessage.addListener(function(request, sender, sendResponse) {
var desc = request.desc;
sendResponse(desc);
});

Chrome Extension: Message Passing (Sending the DOM to popup.js) returns 'null'

I would like to use a Chrome Extension to download the current page's DOM. I'm not sure why, but when my download occurs, the result is just a text file with either 'null' or 'undefined', rather than the DOM. I've tried to assimilate the knowledge from here and here, but I can't seem to get the message from content.js through to popup.js.
Additionally, I'm not sure why this actually works. When I read the docs, it seems like I need to send the message from popup.js to content.js by selecting the active tab:
chrome.tabs.query({currentWindow: true, active: true}, function(tabs) {
chrome.tabs.sendMessage(tabs[0].id, {message: 'getMessageFromContent'}, function(response) {
//Code to handle response from content.js
}
});
My current code:
content.js
var page_html = DOMtoString(document);
chrome.runtime.sendMessage({method: 'downloadPageDOM', pageDOM: thisPage});
function DOMtoString(document_root) { ... }
background.js
chrome.tabs.query({currentWindow: true, active: true}, function(tab) {
var page_html;
chrome.runtime.onMessage.addListener(function(request, sender, sendResponse) {
if (request.message == 'downloadPageDOM')
page_html = request.pageDOM;
else if (request.message == 'getPageDOM')
sendResponse(page_html);
});
});
popup.js
document.addEventListener('DOMContentLoaded', function() {
var download_button = document.getElementById('download_button');
download_button.addEventListener('click', function() {
chrome.runtime.sendMessage({message:'getPageDOM'}, function(response) {
download(response, "download.html", "text/html");
});
});
});
function download(data, fileName, mimeType) { ... }
I feel like I'm missing a crucial understanding of how message passing works. If anyone could take a second to help me understand why the file that downloads just has 'null', I would sincerely appreciate it.
You're over-complicating this, which leads to a lot of logical errors.
You've set up the background page to act like a message proxy, and the content script itself triggers updating your page_html variable. Then the popup pulls that data with another message.
Note that page_html will not contain the current tab's data in any case: you're overwriting this data with the last loaded tab.
What you can do is completely cut out the middleman (i.e. background.js). I guess you got confused by the fact that sending a message TO a popup is a generally a bad idea (no guarantee it's open), but the other way around is usually safe (and you can make it always safe).
Solution 1 (bad, but here for educational purposes)
The logic of your app is: once the user clicks the button, make the snapshot at that moment. So, instead of making your content script do its work immediately, add a message listener:
// content.js
chrome.runtime.onMessage(function(message, sender, sendResponse) {
else if (request.message == 'getPageDOM')
sendResponse(DOMtoString(document));
});
function DOMtoString(document_root) { ... }
And in your popup, request it:
// popup.js
// (Inside the click listener)
chrome.tabs.query({currentWindow: true, active: true}, function(tabs) {
// Note that sending a message to a content script is different
chrome.tabs.sendMessage(tabs[0].id, {message:'getPageDOM'}, function(response) {
download(response, "download.html", "text/html");
});
});
However, this solution is not 100% robust. It will fail if the content script is not injected into the page (and this can happen). But it's possible to fix this.
Solution 2
Let's not assume the content script is injected. In fact, most of the time you don't NEED to inject it automatically, only when the user clicks your button.
So, remove the content script from the manifest, make sure you have host permissions ("<all_urls>" works well, though consider activeTab permission), and the use programmatic injection.
There is a little-used form of programmatic injection that collects the value of the last executed statement. We're going to use that.
// content.js
DOMtoString(document); // This will be the last executed statement
function DOMtoString(document_root) { ... }
In the popup, execute script, collect results:
// popup.js
// (Inside the click listener)
chrome.tabs.query({active: true, currentWindow: true}, function(tabs) {
chrome.tabs.executeScript(tabs[0].id, {file: "content.js"}, function(data) {
// Data is an array of values, in case it was executed in multiple tabs/frames
download(data[0], "download.html", "text/html");
});
});
NOTE: All of the above assumes that your function DOMtoString actually works.

Injected HTML accessing the Chrome API and global variables

I'm working on a Chrome Extension, and I'm new to the process. The extension I'm working on injects an HTML sidebar into the page, injects java-script functions to the header, then let's the user press the buttons on the sidebar to create/save my extension's data.
However, when I want to save the information, I use localStorage, however, the localStorage always saves with respect to the current website. How can I use localStorage to save with respect to our extension?
Furthermore, I would like to use some global javascript variables in the chrome-extension. Where do these belong? I know I can't currently access them from the injected Javascript.
I've looked into message passing, and I've had some trouble with it. I'm not sure how it works in the scope of injected javascript into a page's header. I've tried working with this example, but my extension doesn't seem to catch the message.
// This function is called in the injected header javascript.
function sendToExtension() {
setTimeout(function() {
console.log('page javascript sending message');
window.postMessage({ type: 'page_js_type',
text: "Hello from the page's javascript!"},
'*' /* targetOrigin: any */);
}, 10);
}
// This is installed in a background script.
window.addEventListener('message', function(event) {
console.log('content_script.js got message:', event);
});
You have to use Chrome's sendMessage function and onMessage listener. See below:
function sendToExtension() {
console.log('Sending message');
chrome.runtime.sendMessage({ext: "myExtension"}, function(response) {
console.log(response.ack);
});
}
// Listener - Put this in the background script to listen to all the events.
chrome.runtime.onMessage.addListener(
function(request, sender, sendResponse) {
if (request.ext) {
console.log('Message received from ' + request.ext);
sendResponse({ack:'received'}); // This send a response message to the requestor
}
});

Categories

Resources