I am having some weird results with message passing. I have a content script interacting with a script embedded in my popup page. Currently, they are repeatedly sending the same messages and responses to eachother. I feel like this has something to do with my event listeners firing too much, or maybe the methods i've written to pass the messages are creating some sort of accidental message loop.
My popup script has some logic to inject my content script into every webpage the user visits:
document.addEventListener('DOMContentLoaded', function () {
chrome.tabs.onUpdated.addListener(contextSwitch);
});
//after navigating to a new url
function contextSwitch() {
var tabid = arguments[0];
var changeinfo = arguments[1];
var tab = arguments[2];
//keeps the script from being injected into a tab that's still loading.
if(changeinfo.status === "complete"){
chrome.tabs.executeScript(tabid, {file : "inject.js"}, postInject);
}
else if (changeinfo.status ==="loading"){
console.log("loading fired");
}
else{
console.log("Something went wrong.");
console.log(changeinfo);
}
}
//this fires after the script is injected.
function postInject() {
chrome.runtime.onConnect.addListener(function(port) {
port.onMessage.addListener(messageBroker);
port.postMessage({status:"connected"});
});
}
//helps pick and choose what to do based on nthe message received
function messageBroker() {
var msg = arguments[0];
var sender = arguments[1];
if(msg.status == "initialized"){
sender.postMessage({status:"inject",contents:{notes:page.Notes}});
}
}
The content script opens up a port like this:
var extPort = chrome.runtime.connect({
name : "CS:" + window.location.href
});
extPort.onMessage.addListener(function() {
var msg = arguments[0];
var sender = arguments[1];
//the popup page sends the "connected" message after the port connects.
if (msg.status === "connected") {
var initmsg = {
status : "initialized",
contents : {
url : window.location.href
}
};
sender.postMessage(initmsg);
}
if (msg.status === "inject") {
var contents = msg.contents;
if (contents) {
for (item in contents) {
//do stuff with the contents
}
}
}
sender.postMessage({
status : "received"
});
});
Here is my manifest file:
{
"name": "name",
"version": "0.0.1",
"description": "chrome extension",
"permissions": [
"activeTab",
"tabs","<all_urls>","http://*/","https://*/"
],
"browser_action": {
"default_title": "ext",
"default_icon": "icon.png",
"default_popup": "popup.html"
},
"background": {
"scripts": ["chrome.js"],
"persistent": false
},
"manifest_version": 2,
"content_security_policy": "script-src 'self' https://ajax.googleapis.com; object-src 'self'"
}
My background script is embedded into my popup like this:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>popup</title>
<meta name="description" content="">
<meta name="author" content="alex">
<script src="chrome.js"></script>
</head>
<body style="width:400px;height:400px;">
<div id=menu> Menu </div>
button1
button2
</body>
</html>
My goal for this back and forth message passing is this:
inject the script into any appropriate page
the script tells the background that it's been injected and is ready to receive orders.
the background page gives it orders.
the content script performs some operation based upon those orders.
If I understand correctly, the problem is that you have been mixing up the packground-page and popup code.
The popup page is executed every time you open the popup. This means every time the popup is shown, a couple of new listeners are registered (which continue to live on after the popup is closed). Thus you end up with a dozen identical listeners, responding to an event (which in turn results is moe and most importantly unexpected traffic).
I suggest you take a look at the core concepts of an extension, to help clear things up:
Event Pages (a.k.a. non-persistent Background Pages)
Popups
Content Scripts
Related
I'm writing a simple Chrome extension which will click a button inside an internal website of my company. Specifically, the website contains a button "Show more" which upon click will show more data.
My problem is that when I execute the Javascript needed to click the button in Chrome console everything works. However when I execute it from the content.js script of the extension the button is not clicked.
This is my manifest.json:
{
"manifest_version": 2,
"name": "asd",
"description": "qwe",
"version": "0.1",
"author": "abc",
"browser_action": {
"default_icon": "asd.png",
"default_title": "Have a good day",
"default_title": "asd"
},
"permissions": ["activeTab", "https://ajax.googleapis.com/"],
"content_scripts": [
{
"matches": ["http://*/*"],
"js": ["content.js"],
"run_at": "document_end",
"all_frames": true
}
]
}
the content.js is like this:
window.addEventListener('load', function(event) {
console.log("page load!");
console.log(window.location.href);
console.log()
var pageURL = window.location.href;
if (pageURL.indexOf('example.com')) {
console.log('example.com');
var metaTags = document.querySelectorAll('meta');
for (var i = 0; i < metaTags.length; i++) {
console.log(metaTags[i].getAttribute('content'));
if (metaTags[i].getAttribute('name') === 'application-name' && metaTags[i].getAttribute('content') === 'XYZ') {
console.log('got match');
var buttonClass = document.getElementsByClassName('source-viewer-more-code');
console.log('buttonClass = ' + JSON.stringify(buttonClass));
var button = buttonClass[0];
console.log("button = " + button);
}
}
}
});
I do see all console.log prints, however JSON.stringify(buttonClass) prints an empty object and console.log("buttonw = " + button); prints undefined.
I'm currently running the extension locally. Is this a Chrome permissions issue or something else?
If you can read meta tags,I guess it is not an issue with permissions, Make sure that Button is in DOM when you are trying to get it by "getElementsByClassName"(One possible reason for button not in DOM is some js in the page might be populating the button to DOM after an ajax request). (When you execute in console, you might be executing after the element is populated in DOM ) if you expect a button and it is not in the dom use something like setTimeout and wait for the button to be populated by other js in the page.
I am new to using chrome extensions and i have a run into a little problem with setting up a chrome extension. I want the extension to read specific values from a web page and then open up a specific page (form with a number of input fields) from a flask application that I have built in a new tab and then use the values that have been scraped to populate specific fields in the page from my flask app.
I have managed to get the extension to generate a new tab and to load the page from my flask app but I am unable to get the fields to populate. It would seem that the page gets loaded before the fields are populated. I have pasted some code to show you how far I have got. The other issue is that I am using the code parameter from executeScripts to perform the populating action but I don't seem to be able to pass arguments into the code string (I suspect this is not the way to do this but I am working off an answer that I have found very helpful up to this point from here https://stackoverflow.com/a/41094570/1977981
Any help would be much appreciated.
manifest.json
{
"manifest_version": 2,
"name": "My Cool Extension",
"version": "0.1",
"permissions": [
"http://localhost:****/lesson/1/note/new/"
],
"content_scripts": [
{
"matches": [
"<all_urls>"
],
"js": ["jquery-3.2.1.min.js", "content.js"]
}
],
"browser_action": {
"default_icon": "icon.png"
},
"background": {
"scripts": ["jquery-3.2.1.min.js", "background.js"]
}
}
content.js
// Triggered by sendMessage function in background.js
chrome.runtime.onMessage.addListener(
function(request, sender, sendResponse) {
// listening for request message
if( request.message === "clicked_browser_action" ) {
// Retrieve Lesson title from current tab
var lesson = $('._tabbed-sidebar--title--1Dx5w').find('span').text()
// output this value to the console
console.log(lesson);
// Send a single message to the event listener in your extension i.e. background.js
chrome.runtime.sendMessage({"message": "open_new_tab", "lesson": lesson})
}
}
);
background.js
// Called when the user clicks on the browser action icon.
chrome.browserAction.onClicked.addListener(function(tab) {
// Send a message to the active tab
chrome.tabs.query({active: true, currentWindow: true}, function(tabs) {
// take the current tab shown in the window
var activeTab = tabs[0];
// Send a message to contents.js - this message is being listened for by contents.js and the runtime.onMessage event is fired in content.js script
chrome.tabs.sendMessage(activeTab.id, {"message": "clicked_browser_action"});
});
});
// listening for "open_new_tab" message from content.js
chrome.runtime.onMessage.addListener(
function(request, sender, sendResponse) {
if( request.message === "open_new_tab" ) {
// create new tab but do not activate the tab yet
chrome.tabs.create({"url": "http://localhost:5000/lesson/1/note/new/", active: false }, function(tab){
// load jquery functionality execute script
chrome.tabs.executeScript(tab.id, {file: "jquery-3.2.1.min.js"}, function(results){
chrome.tabs.executeScript(tab.id,{code:`
(function(arguments){
var count = 100; //Only try 100 times
function autofill(){
var lesson = $('.lesson_subsection');
console.log(lesson);
if(lesson){
lesson.value = arguments[0].lesson;
} else {
if(count-- > 0 ){
//The elements we need don't exist yet, wait a bit to try again.
setTimeout(autofill,250);
}
}
}
autofill();
}, request)();
`}, function(results){
chrome.tabs.update(tab.id,{active:true});
}); //END OF second executeScript function
}); // END OF first executeScript function
} // END OF chrome.tabs.create anonymous function
); // END OF chrome.tabs.create
} // END OF if( request.message === "open_new_tab" ) {
}); // END OF addListener anonymous function
Thank you #wOxxOm your comments were very helpful. I was able to use JSON.stringify to load the arguments in to the injected code string. I also had to load the input element from my form using document.getElementsByClassName() instead of using the jquery version of the object. This also meant i didn't have to load the jquery library see line shown below
var less = document.getElementsByClassName('lesson_subsection')[0];
Now my chrome.runtime.onMessage.addListener function in my background.js script is as follows:
// See content.js function
chrome.runtime.onMessage.addListener(
function(request, sender, sendResponse) {
if( request.message === "open_new_tab" ) {
chrome.tabs.create({"url": "http://localhost:5000/lesson/1/note/new/", active: false }, function(tab){
console.log('Attempting to inject script into tab:',tab);
chrome.tabs.executeScript(tab.id,{code:`
(function(argument){
var count = 100; //Only try 100 times
function autofill(){
var less = document.getElementsByClassName('lesson_subsection')[0];
if(less){
less.value = argument;
} else {
if(count-- > 0 ){
//The elements we need don't exist yet, wait a bit to try again.
setTimeout(autofill,250);
}
}
}
autofill();
})(` + JSON.stringify(request.lesson) + `);
`}, function(results){
// chrome.tabs.sendMessage(tab.id, {"message": "need to update tab", "tab": tab.id});
chrome.tabs.update(tab.id, { active: true });
}); //END OF executeScript function
} // END OF chrome.tabs.create anonymous function
); // END OF chrome.tabs.create
} // END OF if( request.message === "open_new_tab" ) {
}); // END OF addListener anonymous function
I am currently creating a WebExtension in which I register a listener on the web requests being made, as such:
main.js:
chrome.webRequest.onBeforeRequest.addListener(main_function, {urls: sites}, ["blocking"]);
where sites is an array containing a list of URLs loaded from a settings page.
Upon changing these settings, which live in separate HTML and JavaScript files, I want to update the aforementioned listener to now contain the new list of sites.
I'm having trouble getting access to this listener from the Settings JavaScript file, as
onBeforeRequest.removeListener(callback)
requires the original callback as an argument.
Does anyone know how I can, from a different JavaScript file, update this listener?
Example of problem (3 files):
manifest.json:
{
...
"permissions": ["webRequest", "webRequestBlocking", "storage"],
"background: { "scripts": ["main.js"] },
"options_ui": { "page":"settings.html" }
}
where settings.html in turn loads settings.js.
main.js:
/* I wont type out the code to load this array from settings. You will have to imagine it being loaded */
all_urls = ["http://www.google.com", "http://www.stackoverflow.com"];
function example_callback() {
console.log("Hello World!");
}
chome.webRequest.onBeforeRequest.addListener(example_callback, {urls: all_urls}, ["blocking"]);
settings.js:
/* Again, imagine that the settings are edited by this file and saved to storage. */
all_urls = ["https://www.google.com"];
/* This is where the problem lies. Listener cannot be removed, as callback is not available in this file */
chrome.webRequest.onBeforeRequest.removeListener(); // Wont work
chrome.webRequest.onBeforeRequest.addListener(example_callback, {urls: all_urls}, ["blocking"]);
Communicating between your options page (or panel) JavaScript and background scripts
There are two general methods of communicating between your options page or a panel, and your background scripts.
You can directly access variables and functions that are in your background scripts from your options page JavaScript by first obtaining the Window object for your background scripts by using extension.getBackgroundPage(). If you want to directly access other pages from your background script you can use extension.getViews() to get the Window object for the background page and, if defined, any popup/panel, option page, or tab containing content which is packaged with the extension.
For the code that is in the question you could do:
let backgroundPage = chrome.extension.getBackgroundPage();
chrome.webRequest.onBeforeRequest.removeListener(backgroundPage.example_callback);
You can send messages back an forth between the pages using runtime.sendMessage(), runtime.onMessage, and/or runtime.connect().
If you send messages back and forth, then you need to make choices as to what those messages will be used for and their contents. Do you send all the data, or just a message that the data was updated? Are you going to use messages for multiple purposes? If so, how are your listener(s) going to determine what message is for which part of your script. You will need to impose some type of format on the messages. The more things that you need to accomplish with these messages, the more complex the format that you need to impose.
Example Code:
The following extension logs web requests to the console. Depending on the users choice, it will log
Nothing
All requests to mozilla.org
All web requests
It implements the same page as both an options_ui page and a default_popup for a browser_action button. The user can select from the above 3 logging options and how the option data is communicated to the background page:
Options are stored to storage.local in the options.js code. Then, the options.js directly invokes the getOptions() function in the background.js file to have the background script re-read the options.
Options are stored to storage.local in the options.js code. Then, the options.js sends a optionsUpdated message to the background script that the options have been updated. The background script then re-reads the options.
A optionsData message is sent from the options.js code to the background page when the options are change which contains a data payload with all of the options. The options are then stored to storage.local in the background script. Once the options are stored, the background script sends a optionsStored message back to the options.js code. The options.js code then indicates to the user that the options have been saved.
Messages that are sent between the background.js and options.js are an object that has the following format:
{
type: //String describing the type of message:
// 'optionsUpdated' 'optionsData', or 'optionsStored'
data: //Object containing the options data
}
//Options data object:
{
loggingUrls: //Array of URL match strings for webRequest requestFilter
useDirect: //Number: 0, 1, 2 indicating the method of communication between
// options.js and background.js
// 0 = Directly invoke functions in background script from options/panel code
// 1 = Send a message that data was updated
// 2 = Send a message with all options data
}
The extension has been testing in both Firefox and Google Chrome:
manifest.json:
{
"description": "Demonstrate Changing webRequest.RequestFilter",
"manifest_version": 2,
"name": "webrequest.requestfilter-demo",
"version": "0.1",
"applications": {
"gecko": {
//Firefox: must define id to use option_ui:
"id": "webrequestrequestfilter-demo#example.example",
"strict_min_version": "42.0",
"strict_max_version": "51.*"
}
},
"permissions": [
"storage",
"webRequest",
"webRequestBlocking",
"<all_urls>" //Required for Google Chrome. Not, currently, needed for Firefox.
],
"background": {
"scripts": [
"background.js"
]
},
"browser_action": {
"default_icon": {
"48": "myIcon.png"
},
"default_title": "Currently NOT logging. Click to start logging only mozilla.org",
"browser_style": true,
"default_popup": "options.html"
},
"options_ui": {
"page": "options.html",
"chrome_style": true
}
}
background.js:
var webRequestExtraInfo = ["blocking"];
var useDirect=0; //Holds the state of how we communicate with options.js
const useDirectTypes=[ 'Directly invoke functions in background script'
,'Send a message that data was updated'
,'Send a message with all options data'];
//Register the message listener
chrome.runtime.onMessage.addListener(receiveMessage);
function receiveMessage(message,sender,sendResponse){
//Receives a message that must be an object with a property 'type'.
// This format is imposed because in a larger extension we may
// be using messages for multiple purposes. Having the 'type'
// provides a defined way for other parts of the extension to
// both indicate the purpose of the message and send arbitrary
// data (other properties in the object).
console.log('Received message: ',message);
if(typeof message !== 'object' || !message.hasOwnProperty('type')){
//Message does not have the format we have imposed for our use.
//Message is not one we understand.
return;
}
if(message.type === "optionsUpdated"){
//The options have been updated and stored by options.js.
//Re-read all options.
getOptions();
}
if(message.type === "optionsData"){
saveOptionsSentAsData(message.data,function(){
//Callback function executed once data is stored in storage.local
console.log('Sending response back to options page/panel');
//Send a message back to options.js that the data has been stored.
sendResponse({type:'optionsStored'});
//Re-read all options.
getOptions();
});
//Return true to leave the message channel open so we can
// asynchronously send a message back to options.js that the
// data has actually been stored.
return true;
}
}
function getOptions(){
//Options are normally in storage.sync (sync'ed across the profile).
//This example is using storage.local.
//Firefox does not currently support storage.sync.
chrome.storage.local.get({
loggingUrls: [''],
useDirect: 0
}, function(items) {
if(typeof items.useDirect !== 'number' || items.useDirect<0 || items.useDirect>2) {
items.useDirect=0;
}
useDirect = items.useDirect;
updateLogging(items.loggingUrls);
console.log('useDirect=' + useDirectTypes[useDirect]);
});
}
function saveOptionsSentAsData(data,callback) {
//Options data received as a message from options.js is
// stored in storeage.local.
chrome.storage.local.set(data, function() {
//Invoke a callback function if we were passed one.
if(typeof callback === 'function'){
callback();
}
});
}
function updateLogging(urlArray){
//The match URLs for the webRequest listener are passed in as an
// array. Check to make sure it is an array, and forward to
// function that adds the listener as a requestFilter.
if(typeof urlArray === "object" && Array.isArray(urlArray)
&& urlArray[0].length>0){
startListeningToWebRequests({urls: urlArray});
}else{
//The argument was not an array
stopListeningToWebRequests();
}
}
function logURL(requestDetails) {
//Log the webRequest to the Console.
console.log("Loading: " + requestDetails.url);
return {}; //Return object in case this is a blocking listener
}
function stopListeningToWebRequests() {
if(chrome.webRequest.onBeforeRequest.hasListener(logURL)) {
//Don't really need to check for the listener, as removeListener for a
// function which is not listening does nothing (no-op).
chrome.webRequest.onBeforeRequest.removeListener(logURL);
console.log("STOPPED logging all Web Requests");
}
}
function startListeningToWebRequests(requestFilter) {
stopListeningToWebRequests();
//Start listening to webRequests
chrome.webRequest.onBeforeRequest
.addListener(logURL,requestFilter,webRequestExtraInfo);
//Log to the console the requestFilter that is being used
console.log("Logging Web Requests:", requestFilter, "-->", requestFilter.urls);
}
//Read the options stored from prior runs of the extension.
getOptions();
//On Firefox, open the Browser Console:
//To determine if this is Chrome, multiple methods which are not implemented
// in Firefox are checked. Multiple ones are used as Firefox will eventually
// support more APIs.
var isChrome = !!chrome.extension.setUpdateUrlData
&& !!chrome.runtime.reload
&& !!chrome.runtime.restart;
if(!isChrome) {
//In Firefox cause the Browser Console to open by using alert()
window.alert('Open the console. isChrome=' + isChrome);
}
options.js:
// Saves options to chrome.storage.local.
// It is recommended by Google that options be saved to chrome.storage.sync.
// Firefox does not yet support storage.sync.
function saveOptions(data, callback) {
chrome.storage.local.set(data, function() {
if(typeof callback === 'function'){
callback();
}
// Update status to let user know options were saved.
notifyOptionsSaved();
});
}
function optionsChanged() {
//Get the selected option values from the DOM
let loggingUrls = document.getElementById('loggingUrls').value;
let useDirectValue = document.getElementById('useDirect').value;
useDirectValue = +useDirectValue; //Force to number, not string
//Put all the option data in a single object
let optionData = {
loggingUrls: [loggingUrls],
useDirect: useDirectValue
}
if(useDirectValue == 0 ) {
//We save the options in the options page, or popup
saveOptions(optionData, function(){
//After the save is complete:
//The getOptions() functon already exists to retrieve options from
// storage.local upon startup of the extension. It is easiest to use that.
// We could remove and add the listener here, but that code already
// exists in background.js. There is no reason to duplicate the code here.
let backgroundPage = chrome.extension.getBackgroundPage();
backgroundPage.getOptions();
});
} else if (useDirectValue == 1) {
//We save the options in the options page, or popup
saveOptions(optionData, function(){
//Send a message to background.js that options in storage.local were updated.
chrome.runtime.sendMessage({type:'optionsUpdated'});
});
} else {
//Send all the options data to background.js and let it be dealt with there.
chrome.runtime.sendMessage({
type:'optionsData',
data: optionData
}, function(message){
//Get a message back that may indicate we have stored the data.
if(typeof message === 'object' && message.hasOwnProperty('type')){
if(message.type === 'optionsStored') {
//The message received back indicated the option data has
// been stored by background.js.
//Notify the user that the options have been saved.
notifyOptionsSaved();
}
}
});
}
}
// Restores select box using the preferences
// stored in chrome.storage.
function useStoredOptionsForDisplayInDOM() {
chrome.storage.local.get({
loggingUrls: [''],
useDirect: 0
}, function(items) {
//Store retrieved options as the selected values in the DOM
document.getElementById('loggingUrls').value = items.loggingUrls[0];
document.getElementById('useDirect').value = items.useDirect;
});
//notifyStatusChange('Option read');
}
function notifyOptionsSaved(callback){
//Notify the user that the options have been saved
notifyStatusChange('Options saved.',callback);
}
function notifyStatusChange(newStatus,callback){
let status = document.getElementById('status');
status.textContent = newStatus;
//Clear the notification after a second
setTimeout(function() {
status.textContent = '';
if(typeof callback === 'function'){
callback();
}
}, 1000);
}
document.addEventListener('DOMContentLoaded', useStoredOptionsForDisplayInDOM);
document.getElementById('optionsArea').addEventListener('change',optionsChanged);
options.html:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>WebRequest Logging Options</title>
<style>
body: { padding: 10px; }
</style>
</head>
<body>
<div id="optionsArea">
Log Web Requests from:
<select id="loggingUrls">
<option value="">None</option>
<option value="*://*.mozilla.org/*">Mozilla.org</option>
<option value="<all_urls>">All URLs</option>
</select>
<br/>
Communication with background page:
<select id="useDirect">
<option value="0">Direct</option>
<option value="1">Message Updated</option>
<option value="2">Message all Data</option>
</select>
</div>
<div id="status" style="top:0px;display:inline-block;"></div>
<script src="options.js"></script>
</body>
</html>
The code in this answer was combined and modified from that in the question I answered here, and my answer here, which is based on code the OP for that question provided in a GitHub repository. The options.js and options.html files had their start with code found on developer.chrome.com.
The solution is to use the WebExtension message API, as such:
settings.js:
...
/* Settings now outdated */
chrome.runtime.sendMessage(message);
...
main.js
...
chrome.runtime.onMessage.addListener( (message) => {
/* Update listener */
chrome.webRequest.onBeforeRequest.removeListener(example_callback);
chrome.webRequest.onBeforeRequest.addListener(example_callback, {urls: all_urls}, ["blocking"]);
});
...
Relevant section from documentation: https://developer.mozilla.org/en-US/Add-ons/WebExtensions/Content_scripts#Communicating_with_background_scripts
I'm attempting to build a Chrome extension that allows the user to grab all of their open URLs and then display it on a new tab (eventually I want to allow the user to send the data to a database). I can capture all of the tabs in the current Window, but I'm having trouble taking that data and using it in the newly created tab.
Here's how my project is configured now:
Manifest.json
{
"name": "ExplodeSlideshow",
"version": "0.0.1",
"manifest_version": 2,
"description" : "This Chrome extension allows you to click on all of the links in a Slideshow (opened in a New Tab) and then save those links to a database.",
"icons": { "16": "icons/16x16.png", "48": "icons/48x48.png", "128": "icons/128x128.png" },
"permissions": [
"tabs"
],
"browser_action": {
"default_icon": {
"19": "icons/19x19.png",
"38": "icons/38x38.png"
},
"default_title": "That's the tool tip",
"default_popup": "browseraction/popup.html"
},
"background": {
"scripts": ["background.js", "populate_content.js"],
"persistent": false
},
// "chrome_url_overrides" : {
// "newtab": "newtab/newtab.html"
// },
"content_scripts": [
{
"matches": ["http://*/*", "https://*/*"],
"js": ["content.js"]
}
]
}
background.js
chrome.extension.onMessage.addListener(function(request, sender, sendResponse) {
switch(request.type) {
case "capture-tabs":
getWindowUrls();
break;
}
return true;
});
var getWindowUrls = function() {
// get current browser window
chrome.windows.getCurrent(function(win) {
// alert(JSON.stringify(win));
// get an array of the tabs in the window
chrome.tabs.getAllInWindow(win.id, function(tabs) {
var activeUrl = "";
var activeUrlId = null;
var urls = [];
for (i in tabs) {
if (tabs[i].active === true) {
// save current tab's id & URL
activeUrl = tabs[i].url
activeUrlId = tabs[i].id
} else {
// capture other tabs' id & URL
urls.push({id: tabs[i].id, url: tabs[i].url});
}
}
// alert(JSON.stringify(activeUrl));
// alert(JSON.stringify(urls));
// open new tab
chrome.tabs.create({active: true, url: 'newtab/newtab.html'}, function (tab) {
alert(JSON.stringify(tab));
// populate HTML
// NEED HELP HERE
// remove original tab
chrome.tabs.remove(activeUrlId, function() {});
// close newly opened tabs
for (i in urls) {
chrome.tabs.remove(urls[i].id, function() {});
}
});
});
});
}
browseraction/popup.html
<script type="text/javascript" src="popup.js"></script>
<div style="width:200px">
<button id="button">Capture Tabs</button>
</div>
browseraction/popup.js
window.onload = function() {
document.getElementById("button").onclick = function() {
chrome.extension.sendMessage({
type: "capture-tabs"
});
}
}
newtab/newtab.html
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css">
<style type="text/css">
</style>
</head>
<body>
<div class="container">
<h1 class="text-center">Explode Slideshows | Chrome Extension</h1>
<h4>Origin Page: <span id="origin"></span></h4>
<ul id="other_tabs">
</ul>
</div>
<script type="text/javascript" src="populate_content.js"></script>
</body>
</html>
newtab/populate_content.js
(function() {
var origin_element = document.getElementById("origin");
var other_tabs_element = document.getElementById("other_tabs");
origin_element.innerHTML += activeUrl;
})();
When I attempt to use this Chrome extension I can click on the button in the popup.html and it grabs all of the tabs, stores them in a variable, opens a new tab, destroys the other tabs. But I have trouble populating the HTML of the newly created tab -- I get Uncaught ReferenceError: activeUrl is not defined. So, my question is how do I pass the JavaScript objects (activeUrl and urls) from the background.js getWindowUrls() method and use to populate HTML in my newtab/newtab.html file?
your new tab isn't directly linked to the background page. So your new tab wont be able to access any variables stored in background.js. you can use messaging, however i find that saving and using a window variable is easier:
example new tab:
chrome.runtime.getBackgroundPage(function(window){
var backgroundpage=window;
backgroundpage.activeUrl;//equals "foo"
backgroundpage.executeFunction("hi");//returns "hello!"
});
example background page:
var activeUrl="foo";
function executeFunction(message){
if(message=="hi"){
return "hello!";//or an object, number, array, ect.
}else{
return "what did you say?";
}
}
Popups also support this. Content scripts dont.
Have your background page save all relevant information into a variable.
When your new tab loads, have it grab that variable and pupulate itself.
The background page should not be in charge of pupulating the html.
I am looking for a function inside a webpage te activate a chrome extension.
Imagine that http://www.example.com/test.html contains:
<script>
hello();
</script>
And my background page contains the definition of the hello function:
function hello() {
alert("test");
}
How can I make sure that the Chrome extension's background page's hello is called when test.html calls hello();?
Before a web page is able to call a background page's function, the following problems need to be solved:
Be able to use hello(); from a web page. This is done by injecting a script defining hello using Content scripts. The injected function communicates with the content script using a custom event or postMessage.
The content script needs to communicate with the background. This is implemented through chrome.runtime.sendMessage.
If the web page needs to receive a reply as well:
Send a reply from the background page (sendMessage / onMessage, see below).
In the content script, create a custom event or use postMessage to send a message to the web page.
In the web page, handle this message.
All of these methods are asynchronous, and have to be implemented through callback functions.
These steps need to be designed carefully. Here's a generic implementation which implements all of the above steps. What you need to know about the implementation:
In the code-to-be-injected, use the sendMessage method whenever the content script need to be contacted.
Usage: sendMessage(<mixed message> [, <function callback>])
contentscript.js
// Random unique name, to be used to minimize conflicts:
var EVENT_FROM_PAGE = '__rw_chrome_ext_' + new Date().getTime();
var EVENT_REPLY = '__rw_chrome_ext_reply_' + new Date().getTime();
var s = document.createElement('script');
s.textContent = '(' + function(send_event_name, reply_event_name) {
// NOTE: This function is serialized and runs in the page's context
// Begin of the page's functionality
window.hello = function(string) {
sendMessage({
type: 'sayhello',
data: string
}, function(response) {
alert('Background said: ' + response);
});
};
// End of your logic, begin of messaging implementation:
function sendMessage(message, callback) {
var transporter = document.createElement('dummy');
// Handles reply:
transporter.addEventListener(reply_event_name, function(event) {
var result = this.getAttribute('result');
if (this.parentNode) this.parentNode.removeChild(this);
// After having cleaned up, send callback if needed:
if (typeof callback == 'function') {
result = JSON.parse(result);
callback(result);
}
});
// Functionality to notify content script
var event = document.createEvent('Events');
event.initEvent(send_event_name, true, false);
transporter.setAttribute('data', JSON.stringify(message));
(document.body||document.documentElement).appendChild(transporter);
transporter.dispatchEvent(event);
}
} + ')(' + JSON.stringify(/*string*/EVENT_FROM_PAGE) + ', ' +
JSON.stringify(/*string*/EVENT_REPLY) + ');';
document.documentElement.appendChild(s);
s.parentNode.removeChild(s);
// Handle messages from/to page:
document.addEventListener(EVENT_FROM_PAGE, function(e) {
var transporter = e.target;
if (transporter) {
var request = JSON.parse(transporter.getAttribute('data'));
// Example of handling: Send message to background and await reply
chrome.runtime.sendMessage({
type: 'page',
request: request
}, function(data) {
// Received message from background, pass to page
var event = document.createEvent('Events');
event.initEvent(EVENT_REPLY, false, false);
transporter.setAttribute('result', JSON.stringify(data));
transporter.dispatchEvent(event);
});
}
});
background.js
chrome.runtime.onMessage.addListener(function(message, sender, sendResponse) {
if (message && message.type == 'page') {
var page_message = message.message;
// Simple example: Get data from extension's local storage
var result = localStorage.getItem('whatever');
// Reply result to content script
sendResponse(result);
}
});
A Chrome extension is not complete without a manifest file, so here's the manifest.json file which I used to test the answer:
{
"name": "Page to background and back again",
"version": "1",
"manifest_version": 2,
"background": {
"scripts": ["background.js"]
},
"content_scripts": [{
"matches": ["http://jsfiddle.net/jRaPj/show/*"],
"js": ["contentscript.js"],
"all_frames": true,
"run_at": "document_start"
}]
}
This extension was tested at http://jsfiddle.net/jRaPj/show/ (containing hello(); as seen in the question), and shows a dialog saying "Background said: null".
Open the background page, use localStorage.setItem('whatever', 'Hello!'); to see that the message is correctly changed.
There is a builtin solution to Send messages from web pages to the extension
mainfest.json
"externally_connectable": {
"matches": ["*://*.example.com/*"]
}
Web page:
// The ID of the extension we want to talk to.
var editorExtensionId = "abcdefghijklmnoabcdefhijklmnoabc";
// Make a simple request:
chrome.runtime.sendMessage(editorExtensionId, {openUrlInEditor: url},
function(response) {
if (!response.success)
handleError(url);
});
Extension's background script:
chrome.runtime.onMessageExternal.addListener(
function(request, sender, sendResponse) {
if (sender.url == blacklistedWebsite)
return; // don't allow this web page access
if (request.openUrlInEditor)
openUrl(request.openUrlInEditor);
});
No, with your above code because of background page(s) architecture
Yes with content scripts
Demonstration Using Content Scripts
manifest.json
Registering content scripts myscripts.js
{
"name": "NFC",
"description": "NFC Liken",
"version": "0.1",
"manifest_version": 2,
"permissions": ["tabs", "http://*/", "https://*/"],
"content_scripts": {
"matches": "http://www.example.com/*",
"js": [ "myscript.js"]
},
"browser_action": {
"default_icon": "sync-icon.png",
"default_title": "I Like I Tag"
}
}
Let me know if you need more information.