Sending message from background script to content script (multiple browsers) - javascript

I have a working prototype of an extension that works on Chrome but when I try to run it on Firefox i get the following error:
Unchecked lastError value: Error: Could not establish connection. Receiving end does not exist.
I use this code to differentiate between browsers:
window.namespace = (function () {
return window.msBrowser ||
window.browser ||
window.chrome;
})();
the following part is to detect when the user clicks on the extension icon (so that I know to activate it):
let show_floater = false; // to know if extension should be active
window.namespace.browserAction.onClicked.addListener(function(tab) {
buttonClicked(tab);
});
function buttonClicked(tab) {
show_floater = !show_floater;
console.log('coding intensifies');
// Send message to content_script of tab.id
window.namespace.tabs.sendMessage(tab.id, show_floater); // <-- ERROR IS HERE
}
All this code is in my background script.
The handling of the message in my content script is as follows
window.namespace.runtime.onMessage.addListener(gotMessage);
let injected = false;
function gotMessage(show_floater, sender, sendResponse) {
// Here I just do stuff
console.log("I'm working here!");
}
Online I saw that people that had this problem usually did not include <all_urls> in the manifest. In my case I already had that so I'm kinda lost here. From my understanding both Chrome and Firefox should use the same methods to send and receive messages. Is my way of distinguishing between browsers flawed?
CHANGES
Here I found a solution.
Background:
chrome.browserAction.onClicked.addListener(function (event) {
chrome.tabs.executeScript(null, {
file: 'js/content.js', /* my content script */ }, () => {
connect() //this is where I call my function to establish a
connection });
});
});
function connect() {
chrome.tabs.query({ active: true, currentWindow: true }, (tabs) => {
const port = chrome.tabs.connect(tabs[0].id);
show_floater = !show_floater;
port.postMessage(show_floater);
// port.onMessage.addListener((response) => {
// html = response.html;
// title = response.title;
// description = response.description;
// });
});
contentscript:
chrome.runtime.onConnect.addListener((port) => {
port.onMessage.addListener((show_floater) => {
else if (!injected) {
injected = true;
let link = document.createElement("link");
link.className = 'beebole_css';
link.href = "https://localhost/css/test.css";
link.type = "text/css";
link.rel = "stylesheet";
document.querySelector("head").appendChild(link);
let s = document.createElement("script");
s.className = 'beebole_js';
s.src = "https://localhost/js/test.js";
s.type = 'text/javascript';
// document.body.appendChild(s);
document.querySelector('body').appendChild(s);
}
});
});
Again, this code works perfectly on Chrome but on Firefox it give the following error:
Loading failed for the <script> with source “https://localhost/js/test.js”.

I suggest you use https://github.com/mozilla/webextension-polyfill for cross browser compatibility.

Related

upgrading chrome extension from manifest version2 to v3, need to get clipboard text in background.js

Hi I am converting Google chrome extension from manifest version-2 to version-3
facing 2 issues those are mentioned below, but before that I will explain what extension in expected to do.
On click specific button on webpage I am calling console application that is copying JSON string in clipboard, then in chrome extension background.js I am getting clipboard data and passing it to content.js which is showing it in web page.
Errors / challenges:
1- Need to get clipboard text into a variable in background.js. I am able to get it in content.js but I need it to get it in background.js
2- I am getting these 2 error in background.js console, but extension is working
Unchecked runtime.lastError: Native host has exited.
Unchecked runtime.lastError: The message port closed before a response was received.
My Background.js looks like this
var port = null;
var tabId = null;
chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
tabId=sender.tab.id;
var hostName = "my.console.app";
port = chrome.runtime.connectNative(hostName);
port.onDisconnect.addListener(onDisconnected);
sendResponse({status: 'ok'});
return true;
});
function onDisconnected() {
port = null;
SendResponse();
}
//this funciton need to upgrade to manifest version 3 because in v3 `chrome.extension.getBackgroundPage` is not compatible
function SendResponse() {
bg = chrome.extension.getBackgroundPage();
bg.document.body.innerHTML = ""; // clear the background page
var helper = null;
if (helper == null) {
helper = bg.document.createElement("textarea");
helper.style.position = "absolute";
helper.style.border = "none";
document.body.appendChild(helper);
}
//Focus the textarea
helper.select();
// perform a Paste in the selected control, here the textarea
bg.document.execCommand("Paste");
// Send data back to content_script
chrome.tabs.sendMessage(tabId, { action: "MY_CUSTOM_EVENT", response: helper.value });
}
content.js
document.addEventListener("MY_CUSTOM_EVENT", function (data) {
chrome.runtime.sendMessage({ runConsoleApp: true }, response => {
});
});
async function copyToTheClipboard(textToCopy){
navigator.clipboard.readText()
.then(text => {
//console.log('Pasted content: ', text);
$('.simulateEidResponse').html(text).trigger('click');
})
.catch(err => {
console.error('Failed to read clipboard contents: ', err);
});
}
Currently there's no way to do it in the background script due to crbug.com/1404835.
In the future the workaround will be execCommand + offscreen API.
The only reliable solution that works regardless of whether the tab is focused or not is to use document.execCommand("Paste") in the content script.
// background.js
chrome.runtime.onMessage.addListener((msg, sender, sendResponse) => {
if (msg.runConsoleApp) {
chrome.runtime.connectNative('my.console.app')
.onDisconnect.addListener(() => sendResponse(true));
return true;
}
});
// content.js
document.addEventListener('MY_CUSTOM_EVENT', async e => {
await chrome.runtime.sendMessage({ runConsoleApp: true });
document.querySelector('.simulateEidResponse').focus();
document.execCommand('selectAll');
document.execCommand('paste');
});

chrome.runtime.sendMessage not working on the 1st click when running normally. it works while debugging though

I have a function in the context.js which loads a panel and sends a message to panel.js at the last. The panel.js function updates the ui on receiving that msg. But it is not working for the first click i.e. it just loads normal ui, not the one that is expected that is updated one after the msg is received. while debugging it works fine.
manifest.json
"background": {
"scripts": ["background.js"],
"persistent": false
},
"content_scripts": [{
"all_frames": false,
"matches": ["<all_urls>"],
"js":["context.js"]
}],
"permissions": ["activeTab","<all_urls>", "storage","tabs"],
"web_accessible_resources":
"panel.html",
"panel.js"
]
context.js - code
fillUI (){
var iframeNode = document.createElement('iframe');
iframeNode.id = "panel"
iframeNode.style.height = "100%";
iframeNode.style.width = "400px";
iframeNode.style.position = "fixed";
iframeNode.style.top = "0px";
iframeNode.style.left = "0px";
iframeNode.style.zIndex = "9000000000000000000";
iframeNode.frameBorder = "none";
iframeNode.src = chrome.extension.getURL("panel.html")
document.body.appendChild(iframeNode);
var dataForUI = "some string data"
chrome.runtime.sendMessage({action: "update UI", results: dataForUI},
(response)=> {
console.log(response.message)
})
}
}
panel.js - code
var handleRequest = function(request, sender, cb) {
console.log(request.results)
if (request.action === 'update Not UI') {
//do something
} else if (request.action === 'update UI') {
document.getElementById("displayContent").value = request.results
}
};
chrome.runtime.onMessage.addListener(handleRequest);
background.js
chrome.runtime.onMessage.addListener((request,sender,sendResponse) => {
chrome.tabs.sendMessage(sender.tab.id,request,function(response){
console.log(response)`
});
});
panel.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<link rel="stylesheet" href="panel.css" />
</head>
<body>
<textarea id="displayContent" rows="10" cols="40"></textarea>
</body>
</html>
Any suggestions on what I am doing wrong or what can I do instead?
An iframe with a real URL loads asynchronously so its code runs after the embedding code finishes - hence, your message is sent too early and is lost. The URL in your case points to an extension resource so it's a real URL. For reference, a synchronously loading iframe would have a dummy URL e.g. no src at all (or an empty string) or it would be something like about:blank or javascript:/*some code here*/, possibly srcdoc as well.
Solution 1: send a message in iframe's onload event
Possible disadvantage: all extension frames in all tabs will receive it, including the background script and any other open extension pages such the popup, options, if they also have an onMessage listener.
iframeNode.onload = () => {
chrome.runtime.sendMessage('foo', res => { console.log(res); });
};
document.body.appendChild(iframeNode);
Solution 2: let iframe send a message to its embedder
Possible disadvantage: wrong data may be sent in case you add several such extension frames in one tab and for example the 2nd one loads earlier than the 1st one due to a bug or an optimization in the browser - in this case you may have to use direct DOM messaging (solution 3).
iframe script (panel.js):
chrome.tabs.getCurrent(ownTab => {
chrome.tabs.sendMessage(ownTab.id, 'getData', data => {
console.log('frame got data');
// process data here
});
});
content script (context.js):
document.body.appendChild(iframeNode);
chrome.runtime.onMessage.addListener(
function onMessage(msg, sender, sendResponse) {
if (msg === 'getData') {
chrome.runtime.onMessage.removeListener(onMessage)
sendResponse({ action: 'update UI', results: 'foo' });
}
});
Solution 3: direct messaging via postMessage
Use in case of multiple extension frames in one tab.
Disadvantage: no way to tell if the message was forged by the page or by another extension's content script.
The iframe script declares a one-time listener for message event:
window.addEventListener('message', function onMessage(e) {
if (typeof e.data === 'string' && e.data.startsWith(chrome.runtime.id)) {
window.removeEventListener('message', onMessage);
const data = JSON.parse(e.data.slice(chrome.runtime.id.length));
// process data here
}
});
Then, additionally, use one of the following:
if content script is the initiator
iframeNode.onload = () => {
iframeNode.contentWindow.postMessage(
chrome.runtime.id + JSON.stringify({foo: 'data'}), '*');
};
document.body.appendChild(iframeNode);
if iframe is the initiator
iframe script:
parent.postMessage('getData', '*');
content script:
document.body.appendChild(iframeNode);
window.addEventListener('message', function onMessage(e) {
if (e.source === iframeNode) {
window.removeEventListener('message', onMessage);
e.source.postMessage(chrome.runtime.id + JSON.stringify({foo: 'data'}), '*');
}
});
one possible way that worked for me is by using functionality in setTimeout() method.
in context.js
setTimeout(() => {
chrome.runtime.sendMessage({action: "update UI", results: dataForUI},
(response)=> {
console.log(response.message)
}
)
}, 100);
But I am not sure if this is the best way.

Firefox add-on pageshow event fires before worker able to receive messages

I'm writing a firefox plugin and keeping track of each page's workers in an array. Apart from a bit of fancy footwork required to manage this array (as described here https://bugzilla.mozilla.org/show_bug.cgi?id=686035 and here Addon SDK - context-menu and page-mod workers) everything is working properly. One issue I'm having is that when listening to the tabs pageshow event (or the worker's own pageshow event for that matter), the callback seems to fire before the worker is actually ready. When retrieving the page's corresponding worker in the callback and using it to try to send a message to the content script, I'm receiving the error The page is currently hidden and can no longer be used until it is visible again. Normally, I'd just use a setTimeout and grit my teeth, but this isn't available for add-ons. What's a suitable workaround? The code for the main part of the add-on is below:
var { ToggleButton } = require('sdk/ui/button/toggle');
var panels = require('sdk/panel');
var tabs = require('sdk/tabs');
var self = require('sdk/self');
var pageMods = require('sdk/page-mod');
var ss = require('sdk/simple-storage');
var workers = [];
ss.storage.isPluginActive = ss.storage.isPluginActive || false;
var button = ToggleButton({
id: 'tomorrowww',
label: 'Tomorowww',
icon: {
'16': './icon-16.png',
'32': './icon-32.png',
'64': './icon-64.png'
},
onChange: handleButtonChange
});
var panel = panels.Panel({
contentURL: self.data.url('panel.html'),
contentScriptFile: self.data.url('panel-script.js'),
onHide: handlePanelHide,
width: 342,
height: 270
});
panel.port.on('panel-ready', handlePanelReady);
panel.port.on('plugin-toggled', handlePluginToggled);
panel.port.on('link-clicked', handleLinkClicked);
pageMods.PageMod({
include: ['*'],
contentScriptFile: [self.data.url('CancerDOMManager.js'), self.data.url('content-script.js')],
contentStyleFile: self.data.url('content-style.css'),
onAttach: function (worker) {
addWorker(worker);
sendActiveState(ss.storage.isPluginActive);
}
});
// move between tabs
tabs.on('activate', function () {
sendActiveState();
});
// this actually fires before the worker's pageshow event so isn't useful as the workers array will be out of sync
//tabs.on('pageshow', function () {
// sendActiveState();
//});
function addWorker (worker) {
if(workers.indexOf(worker) > -1) {
return;
}
worker.on('detach', handleWorkerDetach);
worker.on('pageshow', handleWorkerShown);
worker.on('pagehide', handleWorkerHidden);
workers.push(worker);
}
function handleWorkerDetach () {
removeWorker(this, true);
}
function handleWorkerShown () {
addWorker(this);
// back / forward page history
// trying to send the state here will trigger the page hidden error
sendActiveState();
}
function handleWorkerHidden () {
removeWorker(this);
}
function removeWorker (worker, removeEvents) {
var index = workers.indexOf(worker);
removeEvents = removeEvents || false;
if(index > -1) {
if(removeEvents) {
worker.removeListener('detach', handleWorkerDetach);
worker.removeListener('pageshow', handleWorkerShown);
worker.removeListener('pagehide', handleWorkerHidden);
}
workers.splice(index, 1);
}
}
function getWorkersForCurrentTab () {
var i;
var tabWorkers = [];
i = workers.length;
while(--i > -1) {
if(workers[i].tab.id === tabs.activeTab.id) {
tabWorkers.push(workers[i]);
}
}
return tabWorkers;
}
function handlePanelReady () {
setActive(ss.storage.isPluginActive);
}
function setActive (bool) {
ss.storage.isPluginActive = bool;
panel.port.emit('active-changed', bool);
sendActiveState();
}
function sendActiveState () {
var tabWorkers = getWorkersForCurrentTab();
var i = tabWorkers.length;
while(--i > -1) {
tabWorkers[i].port.emit('toggle-plugin', ss.storage.isPluginActive);
}
}
function handleButtonChange (state) {
if(state.checked) {
panel.show({
position: button
});
}
}
function handlePanelHide () {
button.state('window', {checked: false});
}
function handleLinkClicked (url) {
if(panel.isShowing) {
panel.hide();
}
tabs.open(url);
}
function handlePluginToggled (bool) {
if(panel.isShowing) {
panel.hide();
}
setActive(bool);
}
try using contentScriptWhen: "start" in the page-mod
I was dealing with a similar problem. I think I have it working the way I want now by putting the listener in the content script instead of the addon script. I listen for the event on the window, I then emit a message from my content script to my addon script, my addon script then sends a message back to the content script with the information needed from the addon script.
In my code, I am working on update the preferences in the content script to ensure that the tab always has the most up to date settings when they are changed, only the addon script can listen to the prefs change event.
This particular snippet will listen for when the page is navigated to from history (i.e., back or forward button), will inform the addon script, the addon script will get the most up to date preferences, and then send them back to a port listening in the content script.
Content script:
window.onpageshow = function(){
console.log("onpageshow event fired (content script)");
self.port.emit("triggerPrefChange", '');
};
Addon Script (e.g., main.js:
worker.port.on("triggerPrefChange", function() {
console.log("Received request to triggerPrefChange in the addon script");
worker.port.emit("setPrefs", prefSet.prefs);
});
Since the event is being fired from the DOM event, the page must be shown. I am not sure if listening to the pageshow event in the addon script is doing what we think.

Firefox extension: Getting exception "Permission denied to access property href" for code running in sandbox

I am running the attached code as my firefox extension.
Currently all the extension does is alerting the current location of the page (and iframes) the browser is currently on. For some reason, in some cases I get the following exception when trying to alert the location:
Permission denied to access property href
Unfortunately I can't understand the exact scenario or page the code works and where it throws the exception.
Did anybody encountered this ?
Thanks !
Here is the browser.xul file content (without the HTML tags):
setTimeout(function() {
var appcontent = document.getElementById("appcontent");
if (appcontent) {
appcontent.addEventListener("DOMContentLoaded", function (aEvent) {
var doc = aEvent.originalTarget;
if (!(doc instanceof HTMLDocument)) {
// Validation.
return;
}
var sandbox = new Components.utils.Sandbox(doc.defaultView, {
'sandboxPrototype': doc.defaultView,
'wantXrays': true
});
sandbox.window = doc.defaultView;
sandbox.document = doc.defaultView.document;
sandbox.__proto__ = doc.defaultView;
sandbox.unsafeWindow = doc.defaultView.wrappedJSObject;
var strCode = "try { alert(window.location.href); } catch(e1) {alert(e1.message);}";
Components.utils.evalInSandbox(strCode, sandbox, "1.8");
}, true);
}
}, 1000);

Chrome: message content-script on runtime.onInstalled

I'm the maker of an addon called BeautifyTumblr which changes the apperance of Tumblr.
I wish for my Chrome extension to automatically detect when it has been updated and display changelog to the user. I use an event page with the chrome.runtime.onInstalled.addListener hook to detect when an update has occured, retrieve the changelog from a text file in the extension.. this all works fine, then when I want to forward it to my content script via chrome.tabs.sendmessage it just wont work, nothing ever happens, no errors no nothing. I'm stumped.
Any help would be much appreciated!
Event Page:
chrome.runtime.onInstalled.addListener(function (details) {
"use strict";
if (details.reason === "install") {
} else if (details.reason === "update") {
var thisVersion = chrome.runtime.getManifest().version, xmlDom, xmlhttp;
xmlDom = null;
xmlhttp = new XMLHttpRequest();
xmlhttp.open("GET", chrome.extension.getURL("changelog.txt"), false);
xmlhttp.send(null);
xmlDom = xmlhttp.responseText;
chrome.tabs.query({'url' : 'http://www.tumblr.com/*'}, function (tabs) {
if (tabs.length > 0) {
var mTab = tabs[0].id;
chrome.tabs.update(mTab, {active: true});
setTimeout(chrome.tabs.sendMessage(mTab, {beautifyTumblrUpdate: xmlDom}), 500);
} else {
chrome.tabs.create({'url' : 'http://www.tumblr.com/dashboard'}, function (tab) {
setTimeout(chrome.tabs.sendMessage(tab.id, {beautifyTumblrUpdate: xmlDom}), 500);
});
}
});
}
});
Relevant code in Content Script:
chrome.runtime.onMessage.addListener(
function (request, sender, sendResponse) {
"use strict";
window.alert('test');
if (request.beautifyTumblrUpdate) {
window.alert(request.beautifyTumblrUpdate);
} else if (request.beautifyTumblrInstall) {
window.alert(request.beautifyTumblrInstall);
}
}
);
I am also seeing the same thing. I am not a 100% sure but I think this happens because chrome shuts off connection between background page and "old" content scripts the moment the extension is updated. There's more info here in this bug : https://code.google.com/p/chromium/issues/detail?id=168263
simple, use the following code in background,
chrome.runtime.onInstalled.addListener(function(details){
if(details.reason == "install"){
chrome.tabs.create({ url: chrome.extension.getURL('welcome.html')});
}
});

Categories