I'm trying to automate the task of taking customers data from an ebay page and inserting it into a form in another page. I used Imacros but i don't quite like it.
Are chrome extensions good for this kind of work?
And if yes, where the dom logic should go, on the background page or in the content script?
Can anyone provide me a simple example of code?
NOTE: since January 2021, use Manifest V3 with chrome.scripting.executeScript() and the scripting permission and move <all_urls> to host_permissions instead of using the deprecated chrome.tabs.executeScript() with the tabs permission.
Task
What you need here is a Chrome extension with the ability to read DOM content of the customer page inside a tab with a content script, and then store the information and send it to another tab.
Basically, you'll need to:
Inject a content script in the customer page
Retrieve the data and send it to the background
Elaborate the data and send it to another content script, that will:
Insert the data in the form on another page
Implementation:
So, first of all, your manifest.json will need the permission to access the tabs and the URLs you need, plus the declaration for your background script, something like this:
{
"manifest_version": 2,
"name": "Extension name",
"description": "Your description...",
"version": "1",
"permissions": [
"<all_urls>",
"tabs"
],
"background": { "scripts": ["background.js"] }
}
Now, following the steps:
Add a listener to chrome.tabs.onUpdated to find the customer page and inject the first content script, plus find the tab with the form and inject the second script, all from background.js:
chrome.tabs.onUpdated.addListener(function(tabID, info, tab) {
if (~tab.url.indexOf("someWord")) {
chrome.tabs.executeScript(tabID, {file: "get_data.js"});
// first script to get data
}
if (~tab.url.indexOf("someOtherWord")) {
chrome.tabs.executeScript(tabID, {file: "use_data.js"});
// second script to use the data in the form
}
});
Ok, now the above code will inject your get_data.js script in the page if its URL contains "someWord" and use_data.js if its URL contains "someOtherWord" (you must obviously replace "someWord" and "someOtherWord" with the right words to identify the correct page URLs).
Now, in your get_data.js you'll need to retrieve data and send it to the background.js script, using chrome.runtime.sendMessage, something like this:
var myData = document.getElementById("some-id").textContent;
// just an example
chrome.runtime.sendMessage({messgae: "here is the data", data: myData});
Now you've sent the data, so inside background.js you'll need a listener to catch and elaborate it:
chrome.runtime.onMessage.addListener(function(request, sender, sendResponse) {
if (request.message = "here is the data") {
// elaborate it
chrome.tabs.query({url: "*://some/page/to/match/*"}, function(tabs) {
chrome.tabs.sendMessage(tab[0].id, {message: "here is the data", data: request.data});
});
}
});
Ok, you are almost finished, now you'll need to listen to that message in the second content script, which is use_data.js, and use it in the form:
chrome.runtime.onMessage.addListener(function(request, sender, sendResponse) {
if (request.message == "here is the data") {
// use the data to do something in the page:
var myData = request.data;
// for example:
document.getElementById("input-id").textContent = data;
}
});
And you are done!
Documentation
This wast just an example, and I strongly recommend you to check out the documentation to fully understand the functions and methods to use, here are some useful links:
chrome.tabs
.query
.onUpdated
.sendMessage
.executeScript
chrome.runtime
.onMessage
.sendMessage
Chrome extension message passing
Related
I am trying to build a Chrome plugin (76.0.3809.132 version of Chrome) that:
Injects a JavaScript block in a web page (done)
Get the injected block to pass back message to the chrome plugin using the latest Chrome runtime functionality (problem)
I initially referred to the Chrome developer reference at: https://developer.chrome.com/extensions/messaging#external-webpage
Accordingly, my Manifest is:
{
"manifest_version": 2,
"name": "Message Passing",
"description": "Simple messaging plugin",
"version": "1.1",
"externally_connectable": {
"matches": ["*://*.myexample.com/*"]
},
"permissions": [
"activeTab",
"storage",
"tabs",
"*://*.myexample.com/*"
],
"content_scripts": [
{
"matches": ["*://*.myexample.com/*"],
"run_at": "document_start",
"js": ["myContent.js"]
}
],
"web_accessible_resources": ["injected.js"]
}
My content script:
var s = document.createElement('script');
s.src = chrome.extension.getURL('injected.js');
s.onload = function() {
this.remove();
};
(document.head || document.documentElement).appendChild(s);
//injecting correctly till this point
chrome.runtime.onMessageExternal.addListener(
function(request, sender, sendResponse) {
console.log("In plugin");//never gets called
});
JavaScript within the web page that makes the call to plugin:
//the function called by my web page, sending a JSON or text message
async function sendMessageToPlugin(dataToSend){
console.log('Send to plugin');//this is getting printed
var extID = 'abcdefghijklmnoabcdefhijklmnoabc';
chrome.runtime.sendMessage(extID, dataToSend);
}
No error, no result. I have tried different things, trying to create a background script to listen instead of listening in content script, trying to create a port for persistent messages instead, using "onMessage" instead of "onMessageExternal" per various older posts on the forum, but nothing works :(. What am I doing wrong? Can somebody at least point me towards any actually working example of a web page successfully communicating with the Chrome plugin? Would be good to know what Manifest was used and which part of the extension listened to the message successfully (content script or background). Thank you for your help!
Update 1:
I had some success with using the method described at https://developer.chrome.com/extensions/content_scripts#host-page-communication. Is this the only way of doing this? I would prefer to do it using externally connected functionality, but happy to keep testing this method and run with it if it works.
Update 2:
This code finally works for me:
In my content script-
window.addEventListener("message", function(event) {
// We only accept messages from ourselves
if (event.source != window)
return;
//if (event.data.type && (event.data.type == "FROM_PAGE")) {
console.log("Content script received: ");
console.log(event.data);
//}
}, false);
And in my injected script:
async function sendMessageToPlugin(dataToSend) {
console.log('Send to plugin');
window.postMessage(dataToSend, "*");
}
I am sure Jafar's solution will work as well (I will try it and update!), but for now I am sticking with this one. Thank you Jafar for taking out time to reply!
I am currently trying to make a chrome extension that lists all of the open tabs in its popup window. With more functionality to be added later, such as closing a specific tab through the popup, opening up a new tab with a specific URL etc.
manifest.json
{
"manifest_version": 2,
"name": "List your tabs!",
"version": "1.0.0",
"description": "This extension only lists all of your tabs, for now.",
"background": {
"persistent": true,
"scripts": [
"js/background.js"
]
},
"permissions": [
"contextMenus",
"activeTab",
"tabs"
],
"browser_action": {
"default_popup": "popup.html"
}
}
background.js
const tabStorage = {};
(function() {
getTabs();
chrome.tabs.onRemoved.addListener((tab) => {
getTabs();
});
chrome.tabs.onUpdated.addListener((tab) => {
getTabs();
});
}());
function getTabs() {
console.clear();
chrome.windows.getAll({populate:true},function(windows){
windows.forEach(function(window){
window.tabs.forEach(function(tab){
//collect all of the urls here, I will just log them instead
tabStorage.tabUrl = tab.url;
console.log(tabStorage.tabUrl);
});
});
});
chrome.runtime.sendMessage({
msg: "current_tabs",
data: {
subject: "Tabs",
content: tabStorage
}
});
}
popup.js
(function() {
chrome.runtime.onMessage.addListener(
function(request, sender, sendResponse) {
if (request.msg === "current_tabs") {
// To do something
console.log(request.data.subject)
console.log(request.data.content)
}
}
);
}());
From my understanding, since you're supposed to have listeners in background.js for any changes to your tabs. Then when those occur, you can send a message to popup.js
As you can see, for now I'm simply trying to log my tabs in the console to make sure it works, before appending it to a div or something in my popup.html. This does not work, however, because in my popup.html I'm getting the following error in the console:
popup.js:3 Uncaught TypeError: Cannot read property 'sendMessage' of undefined
so I'm... kind of understanding that I can't use onMessage in popup.js due to certain restrictions, but I also have no clue, then, on how to achieve what I'm trying to do.
Any help would be appreciated.
The Google's documentation about the background script is a bit vague. The important thing for your use case is that the popup runs only when it's shown, it doesn't run when hidden, so you don't need background.js at all, just put everything in popup.js which will run every time your popup is shown, here's your popup.html:
<script src="popup.js"></script>
The error message implies you were opening the html file directly from disk as a file:// page, but it should be opened by clicking the extension icon or via its own URL chrome-extension://id/popup.html where id is your extension's id. This happens automatically when you click the extension icon - the popup is a separate page with that URL, with its own DOM, document, window, and so on.
The popup has its own devtools, see this answer that shows how to invoke it (in Chrome it's by right-clicking inside the popup, then clicking "inspect").
Extension API is asynchronous so the callbacks run at a later point in the future, after the outer code has already completed, which is why you can't use tabStorage outside the callback like you do currently. More info: Why is my variable unaltered after I modify it inside of a function? - Asynchronous code reference.
There should be no need to enumerate all the tabs in onRemoved and onUpdated because it may be really slow when there's a hundred of tabs open. Instead you can modify your tabStorage using the parameters provided to the listeners of these events, see the documentation for details. That requires tabStorage to hold the id of each tab so it would make sense to simply keep the entire response from the API as is. Here's a simplified example:
let allTabs = [];
chrome.tabs.query({}, tabs => {
allTabs = tabs;
displayTabs();
});
function displayTabs() {
document.body.appendChild(document.createElement('ul'))
.append(...allTabs.map(createTabElement));
}
function createTabElement(tab) {
const el = document.createElement('li');
el.textContent = tab.id + ': ' + tab.url;
return el;
}
I found several approaches, but they seem outdated or simply won't work for some reasons for me. Maybe tunnelvision:
First things first:
I have the correct permissions in my manifest.json, I think:
"permissions": [
"tabs",
"activeTab"
]
I have 2 simple scripts, background.js and content.js (which are recognized correctly, the error can't be here).
In my background.js I tried several approaches:
chrome.browserAction.onClicked.addListener(buttonClicked);
var sharedUrl;
function buttonClicked(tab) {
chrome.tabs.getCurrent(function(tab) {
// please read further, this was my last resort, I tried other stuff as well
sharedUrl = console.log(window.location.href);
});
let msg = {
txt: "Hello",
url: sharedUrl
}
chrome.tabs.sendMessage(tab.id, msg);
}
I tried it with getCurrent() and then tab.url, but that didn't work (neither with tab[0].url
I tried it also with getSelected() as well as with something like this:
chrome.tabs.query({active: true, currentWindow: true}, function(arrayOfTabs) {
var activeTab = arrayOfTabs[0];
});
and my content.js is simply this here:
chrome.runtime.onMessage.addListener(gotMessage);
function gotMessage(message, sender, sendResponse) {
console.log(message.txt);
console.log(message.url);
}
It displays "Hello", but not the URL I'm looking for.
Edit:
It might of importance, that I want to retrieve the url after a button-click in my extension.
Thanks for the feedback and help.
Ok, based on the documentation you are not able to grab the tab object while you are not in the tab context. The tab context includes only content scripts. So you can't access to tab because you are calling it from your backend page. You can only do it, if your extension has generated the tab.
Gets the tab that this script call is being made from. May be
undefined if called from a non-tab context (for example: a background
page or popup view).
So, the only possible way is to change your extension data flow.
I'm toying around with Chrome trying to create my first extension. Basically, I want to create a script that does some DOM manipulation on a particular domain. Furthermore, I want the user to be able to toggle the script through an icon displayed in the address bar, when visiting that particular domain.
So far, I've got this manifest.json:
{
"manifest_version": 2,
"name": "Ekstrafri",
"description": "Removes annoying boxes for paid articles.",
"version": "1.0",
"page_action": {
"default_title": "Foobar"
},
"content_scripts": [
{
"matches": ["http://ekstrabladet.dk/*"],
"js": ["jquery.min.js", "cleaner.js"],
"run_at": "document_end"
}
]
}
cleaner.js contains a couple of jQuery DOM selectors that removes some stuff.
The current setup works, but the context script is injected all the time. I want the user to be able to toggle, which should trigger a confirmation prompt in which the user accepts or rejects a page reload.
Anyway, page_action doesn't seem to display any icon. According to the documentation, it should display an icon in the address bar.
I have two questions:
How do I display this page_action icon on the matched content?
How do I bind an event to that icon?
One thing you could do here is get and set a variable in a background.js page using the message passing framework. Essentially when the content-script runs you can contact the background script to check the state of a boolean variable. You can then determine whether or not to execute the rest of the script.
chrome.extension.sendMessage({ cmd: "runScript" }, function (response) {
if (response == true) {
//Run the rest of your script inside here...
}
});
You would also use this initial call to bind some UI on the page so you can toggle this state (i.e. switch the content script on/off). In this example I'm using a checkbox.
$("chkOnOff").click(function(){
var scriptOn = ($(this).is(":checked");
chrome.extension.sendMessage({ cmd: "updateRunScriptState", data: { "scriptOn" : scriptOn } }, function (response) {
//Update confirmed
});
});
The background.js page would look something like this:
//Global variable in background.js
var scriptOn = true;
chrome.extension.onMessage.addListener(
function (request, sender, sendResponse) {
if (request.cmd == "runScript") {
sendResponse(scriptOn);
}
if (request.cmd == "updateRunScriptState") {
//here we can update the state
scriptOn = request.data.scriptOn;
}
});
And don't forget to register the background.js page in the manifest.
..
"background": {
"scripts" : ["background.js"]
},
..
I've been googling around extensively trying to remedy this problem but can't seem to find a solution. I'm trying to do the simple task of setting up a listener and sender in my Chrome extension.
My manifest
{
"manifest_version": 2,
"name": "my app",
"description": "text",
"version": "0.1",
"background":{
"scripts":["background.js"]
},
"content_scripts": [
{
// http://developer.chrome.com/extensions/match_patterns.html
"matches": ["http://myurl.com/*"],
"js": ["jquery-1.9.1.min.js", "myapp.js"],
"all_frames": true
}
],
"browser_action": {
"default_icon": "/icons/icon-mini.png",
"default_popup": "popup.html"
}
}
In my background JS
chrome.tabs.getSelected(null, function(tab) {
chrome.tabs.sendMessage(tab.id, {greeting: "hello"}, function(response) {
console.log(response.farewell);
});
});
In my popup.js (rendered by coffeescript, please forgive the sort of strange syntax)
(function() {
$(function() {});
chrome.extension.onMessage.addListener(function(request, sender, sendResponse) {
if (console.log(sender.tab)) {
"from a content script:" + sender.tab.url;
} else {
"from the extension";
}
if (request.greeting === "hello") {
return sendResponse({
farewell: "goodbye"
});
}
});
}).call(this);
In my myapp.js
chrome.extension.sendMessage({
greeting: "hello"
}, function(response) {
return console.log(response.farewell);
});
I've followed the tutorial. Not sure why this isn't working. I'm pretty decent with JS, very unclear as to why this is behaving strangely. Any help would be hugely appreciated!
There is more than one problem with this code so let me break it down.
From what I see you are trying to send a message from your content script to your popup and there is a background page not doing anything.
Problem #1
The code in the popup.js, besides being strangely convoluted, is not a background page. It only runs when the popup is open, so it will not be able to listen for the message.
Problem #2
The code in the background page is using the depreciated getSelected method to send a message to the content script. The content script has no listener.
The result of these two things is this:
Background page -> content script (no listener)
Content Script -> extension pages (no listener)
I suggest making your background page the hub of your communications. If you need to communicate between your popup and content script make it popup -> content script and use sendResponse() to reply.
Edit: Here is an example of the message passing you would want. Just replace with your variables.
Content Script
...
//get all of your info ready here
chrome.extension.onMessage.addListener(function(message,sender,sendResponse){
//this will fire when asked for info by the popup
sendResponse(arrayWithAllTheInfoInIt);
});
Popup
...
chrome.tabs.query({'active': true,'currentWindow':true},function(tab){
//Be aware 'tab' is an array of tabs even though it only has 1 tab in it
chrome.tabs.sendMessage(tab[0].id,"stuff", function(response){
//response will be the arrayWithAllTheInfoInIt that we sent back
//you can do whatever you want with it here
//I will just output it in console
console.log(JSON.stringify(response));
});
});
I had a similar problem in a background page and my solution was to ensure that the tab had completed loading before trying to send it a message.
If the tab has not fully loaded, the content script will not have started and will not be waiting for messages yet.
Here's some code:
chrome.tabs.onUpdated.addListener(function(tabId, changeInfo, tab) {
if (changeInfo.status === 'complete') {
// can send message to this tab now as it has finished loading
}
}
So if you want to send a message to the active tab, you can make sure it has completed loading first.