I know it's possible to postMessage from a web page to a chrome extension and get an ok response back to the web page, but is it possible the other way around ?
My question "I need to be able to sent a request into a webpage and wait for a response that I can use in the extension. Maybe not with postMessage ?"
I know the docs says "Only the web page can initiate a connection." (https://developer.chrome.com/extensions/messaging#external-webpage)
This is what I have tried so far.
background.js
chrome.extension.sendMessage("Hello from extension to webpage");
window.postMessage("Hello from extension to webpage");
chrome.tabs.sendMessage(TAB_ID, "Hello from extension to webpage");
webpage.js (not part of the extension)
window.onmessage = (event) => {
// Waiting for that message.
console.info("Event received ", event);
}
window.chrome.runtime.connect(CHROME_EXTENSION_APP_ID).onMessage.addListener(function (){
function(request, sender, sendResponse) {
console.info("Event received from the extension", event);
}
})
Any idea will be appreciated :)
You can use chrome.tabs.executeScript API to do this.
var dataToWebPage = {
text: 'test',
foo: 1,
bar: false
};
chrome.tabs.executeScript({
code: '(' + function(params) {
//This function will work in webpage
console.log(params); //logs in webpage console
return {
success: true,
response: "This is from webpage."
};
} + ')(' + JSON.stringify(dataToWebPage) + ');'
}, function(results) {
//This is the callback response from webpage
console.log(results[0]); //logs in extension console
});
Please note that it needs tabs permission.
Related
In my Chrome extension, I'm trying to exchange data between an internal web page of the extension chrome-extension://myExtensionId/path/to/web/page.html and content scripts.
So, in order to make this data persistent among different content scripts, I'm trying to save it as global variables in the extension's background! I do so using message passing.
My problem is:
When I try to send a response back from the background I get this error:
Error in event handler for (unknown): TypeError: sendResponse is not a
function
I followed the documentation's examples and this is my attempt:
In the scriptOfTheInternalPage.js :
var message = {
'order': 'setData',
'varName': 'myArray',
'data': myArray
};
extPort.postMessage(message, function (response) {
console.log('response:\n', JSON.stringify(response));
});
In background.js :
var globals = {
'myArray': [],
...
};
chrome.runtime.onConnect.addListener(function (port) {
port.onMessage.addListener(
function (message, sender, sendResponse) {
console.log(
'the port received this message:\n', JSON.stringify(message), '\n',
(sender.tab) ? ' from tab #' + sender.tab.id : ' from the extension!'
);
if (message.order === 'setData') {
globals[message.varName] = message.data;
sendResponse({'response': 'data saved!'}); //<=====
}
return true; //<=== tried to return true here as well;
});
});
Does this error means I should create a brand new function outside of the onMessage event listener?
I'm confused! What am I missing?
Port's onMessage event listeners do not have the same signature as runtime.onMessage. You don't get sender and sendResponse parameters, only the message. Returning true has no effect either.
To reply to a message, you need to use the port itself. This is covered by examples:
port.onMessage.addListener(function(msg) {
if (msg.joke == "Knock knock")
port.postMessage({question: "Who's there?"});
}
So you do need an onMessage listener on both sides, and some way to track requests (unique ID?) if several can be made.
My intention is for one message to be passed to the worker after it is created, and for that message to be logged by the worker and a reply to be sent back. What happens is that the message sent to the web worker is logged twice, and only one reply is sent. If you refresh the page or pause execution using developer tools, the message is correctly logged once.
I created a miniature extension to demonstrate this. There is a background script listening for a click on the browser action button. When it is clicked, a new tab is opened, and this tab generates 2 web workers. These workers listen for messages and log them, and send a reply stating that the message was recieved. The newly opened tab then sends the message, and logs the reply.
Google drive with all of the files needed to run this as a chrome extension
Image demonstrating the double logging of the message
Some references:
general web worker
use
.postMessage to
worker
chrome browser action
chrome tabs API
I have been working on my extension for a while now and have run into all sorts of fun bugs, particularly with async code. In the end I was always able to either debug them myself or find a reference that explained the problem. Thank you for any help you can give!
background.js
const tabUrl = chrome.extension.getURL("tab.html");
function browserActionCallback(tab) {
function createResultsTab() {
chrome.tabs.create({
"url": tabUrl,
"index": tab.index + 1
}, function() {
console.log("Tab created");
});
}
(function tabHandler() {
chrome.tabs.query({"url":tabUrl}, (tabQueryResults) => {
if (tabQueryResults.length > 0) {
chrome.tabs.remove(tabQueryResults[0].id, () => {
createResultsTab();
});
} else {
createResultsTab();
}
});
})();
}
chrome.browserAction.onClicked.addListener((tab) => {
browserActionCallback(tab);
});
tab.js
function createWorkers(num) {
/*in original extension I intended to let the user change number of workers in options*/
var workers = new Array(num);
for (let i = 0; i < num; i++) {
workers[i] = new Worker("worker.js");
}
return workers;
}
function messageWorkers(workers) {
let len = workers.length
for (let i = 0; i < len; i++) {
let message = "Connection " + i + " started";
workers[i].postMessage(message);
workers[i].onmessage = function(e) {
console.log(e.data);
}
}
}
var numWorkers = 2;
const WORKERS = createWorkers(numWorkers);
console.log("Before");
messageWorkers(WORKERS);
console.log("After");
worker.js
onmessage = function(msg) {
console.log(msg.data);
postMessage("Worker reply- " + msg.data);
}
EDIT 1: changing the tab.js messageWorkers function's for loop such that onmessage is set before postMessage does not change the results. Sending multiple messages also doesn't change results. If I have a console.log statement at the start of worker's onmessage function which logs that the function has begun, it too logs twice. To reiterate, this only happens when this tab is created by the background script and not when the page is refreshed or debugger is used.
EDIT 2: in the devtools console, there is a drop down menu to choose between top and workers. Checking the option 'selected context only' makes the repeated logging disappear, however this view is a little narrower than I would like
I can confirm this issue. Sample code to reproduce it:
index.js
function worker() {
console.log('this logged twice');
}
const workerBlob = new Blob(
[worker.toString().match(/function[^{]+\{([\s\S]*)\}$/)[1]],
{type: 'text/javascript'}
);
const workerBlobUrl = URL.createObjectURL(workerBlob);
new Worker(workerBlobUrl);
Use index.js as a Chrome extension background script and "this logged twice" will be logged twice when loading the extension or re-launching Chrome.
The Question is in continuation to the link given below.
Getting the source HTML of the current page from chrome extension
The top answer given in the above URL gets the source code of the present tab. But this does not get the source code inside the iframes of the source code.
How do I access the source code the entire page INCLUDING FRAMES in chrome extensions?
Use allFrames: true parameter of chrome.tabs.executeScript to inject the code into all frames. The callback function will receive an array with the results for all frames:
popup.js:
chrome.tabs.executeScript({
allFrames: true,
code: 'JSON.stringify({ \
url: location.href, \
html: document.documentElement.innerHTML \
})'
}, function(results) {
if (chrome.runtime.lastError || !results || !results.length) {
console.log("Some error occurred", chrome.runtime.lastError);
} else {
results.forEach(function(str, index) {
var info = JSON.parse(str);
console.log("Frame #%d | %s | %s",
index, info.url, info.html.substr(0, 200) + "...");
});
}
});
I have a chrome extension that toggles a sidebar with the browser action click. The sidebar contains an iframe with a local (chrome extension) source. I thought the page within the iframe would be considered a local chrome extension file with open access to the chrome APIs and etc. However, I keep getting the following errors in the web console:
Uncaught TypeError: Cannot read property 'onClicked' of undefined <-- background.js
TypeError: Cannot read property 'query' of undefined <-- sidebar.js
How do I get it so that the iframe injected with a context script has access to the local chrome environment?
Code below:
sidebar.js:
var myApp = angular.module('PlaceMates', []);
myApp.service('pageInfoService', function() {
this.getInfo = function(callback) {
var model = {};
chrome.tabs.query({
'active': true, // Select active tabs
lastFocusedWindow: true // In the current window
},
function (tabs) {
if (tabs.length > 0)
{
model.title = tabs[0].title;
model.url = tabs[0].url;
chrome.tabs.sendMessage(tabs[0].id, { 'action': 'PageInfo' }, function (response) {
model.pageInfos = response;
console.log("popup js: " + model.pageInfos);
callback(model);
});
}
});
};
});
myApp.controller("PageController", function ($scope, pageInfoService) {
$scope.message = "This extension identifies the photos on this page!";
pageInfoService.getInfo(function (info) {
$scope.title = info.title;
$scope.url = info.url;
$scope.pageInfos = info.pageInfos;
$scope.place_name = info.place_name;
$scope.$apply();
});
});
background.js
console.log( 'Background.html starting!' );
// Called when the user clicks on the browser action.
chrome.browserAction.onClicked.addListener(function(tab) {
// No tabs or host permissions needed!
console.log('Toggling sidebar on ' + tab.url);
// send message to current tab when clicked
var tabId = tab.id;
console.log("tab.id: " + tabId);
chrome.tabs.query({active: true, currentWindow: true}, function(tab) {
chrome.tabs.sendMessage(
//Selected tab id
tabId,
//Params inside a object data
{callFunction: "toggleSidebar"},
//Optional callback function
function(response) {
console.log(response);
}
);
});
console.log('Done toggling sidebar!');
});
chrome.extension.onMessage.addListener(function(request, sender, sendResponse) {
if(request.cmd == "read_file") {
$.ajax({
url: chrome.extension.getURL("sidebar.html"),
dataType: "html",
success: sendResponse
});
}
})
chrome.runtime.onMessage.addListener(
function(msg, sender, sendResponse) {
console.log(sender.tab ?
"from a content script:" + sender.tab.url :
"from the extension");
if (msg.command == "read_info"){
console.log(JSON.parse(JSON.stringify(msg.myInfo)));
sendResponse({command: "done"});
}
}
);
console.log( 'Background.html done.' );
The only pages that have access to all chrome.* extension API are pages that are run at the extension's origin and within the Chrome extension process.
When an extension page is embedded in an iframe, its extension runtime is equivalent to a content script: The page can only use cross-origin XMLHttpRequest and some of the extension APIs (messaging and some other methods in the chrome.runtime / chrome.extension namespace).
If you wish to make the functionality of the other Chrome APIs available to your iframe, then you have to call these APIs from the background page, and use the messaging API to proxy requests from the iframe to the background page and back. Luckily, most of the Chrome extension API is asynchronous by design, so it will not be difficult to change your code to use these proxies.
I'm so close to finishing my Chrome extension. I have one or two things to do. One of them is sending a message from the content script to the background script. I wrote the following, but it doesn't quite what I want.
content.js
var a=document.getElementsByTagName('a');
for (i=0,len=a.length;i<len;i++) {
a[i].addEventListener('contextmenu', function() {
var linkTitle = this.getAttribute('title').trim();
var linkUrl = this.getAttribute('href');
if ((linkTitle != null) && (linkTitle.length > 0)) {
chrome.extension.sendMessage({action:'bookmarkLink', 'title':linkTitle, 'url': linkUrl}, function(msg) {
alert('Messages sent: '+action+' and '+linkTitle+' also '+linkUrl);
});
}
});
};
background.js
chrome.contextMenus.create({'title': 'Add to mySU bookmarks', 'contexts': ['link'], 'onclick': mySUBookmarkLink});
function mySUBookmarkLink(info, tab) {
chrome.extension.onMessage.addListener(function(msg, sender, sendResponse) {
if (msg.action == 'bookmarkLink') {
chrome.storage.sync.set({'title': msg.linkTitle, 'url': msg.linkUrl}, function(msg) {
alert('Saved '+msg.linkTitle+' to bookmarks');
});
}
});
};
My problems are:
In the first code block, it alerts Saved undefined to bookmarks as soon as I right click on the link, while as I understand it should only send a message on right click and the second code block should alert Saved to bookmarks when I click on the context menu. What am I missing or doing wrong?
I may not have used parameters correctly (I am fairly new to extension development and Javascript in general). Do the above look okay?
Thank you in advance,
K.
It's chrome.runtime.sendMessage and chrome.runtime.onMessage rather than chrome.extension.
There used to be chrome.extension.sendRequest and chrome.extension.onRequest which have been deprecated in favor of the chrome.runtime API methods mentioned above.
See Chrome Extensions - Message Passing
it's JSON-serializable messaging, where first pair is for recognition, and then followed by pairs of
key: value.
You pull the value from received message by calling it's key.
is should be:
alert('Saved '+msg.title+' to bookmarks');
or even better:
function mySUBookmarkLink(info, tab) {
chrome.extension.onMessage.addListener(function(msg, sender, sendResponse) {
if (msg.action == 'bookmarkLink') {
var receivedValue = msg.title; //pull it out first, for better overview
chrome.storage.sync.set({'title': msg.title, 'url': msg.url}, function(msg) {
alert('Saved '+receivedValue+' to bookmarks');
});
}
});
};