I would like to get elements from chrome tab with a certain URL, it does not have to be active. so far I have:
Test()
function Test() {
chrome.tabs.query({url: "https://www.somewebsite.com/*"}, function(results) {
chrome.tabs.executeScript(results[0].id,{code: 'El = document.getElementsByClassName("someclass")'});
console.log(El);
})
}
Maybe it has to be done through a content script file?
I have this code placed in my background.js file. Given the proper URL and Class this function will not return the Element. Why?
Thanks for any suggestions!
The background script is executed only once when chrome launches. Instead of writing this inside a function which you call in the same file, you have to put it in some kind of event listener. This code should stay in the background file, but has to be triggered by something, not just executed on extension load.
EDIT: Oh, I see, you're trying to pass the element to the background script. You can't.
Background scripts have access to the whole chrome APIs but not to the page content. The script you write in executeScript runs on the tab you specified, so the variables are available to other code within that tab, not within your background script. Extension code that can run on the page and edit its content is either sent using executeScript or put in content scripts.
If you want to share information between various layers of an extension, you need to use messages. They work like events and listeners. Read the docs here. You'll be able to pass data like numbers and strings, but not the actual HTMLElement. So any code that manipulates the DOM has to run on the tab itself.
If your code is small and simple, you could just write it in the executeScript call instead.
You are correct, there does not appear to be a way to manipulate elements in another tab from the background.js;
Test();
function Test() {
chrome.tabs.query({
url: "https://www.google.com/*"
}, function(results) {
if (results.length < 1) { //if there is no tab open with google.com
console.log("Tab not found, creating new tab");
chrome.tabs.create({
"url": "https://www.google.com/",
"selected": true
}, null);
chrome.tabs.query({
url: "https://www.google.com/*"
}, function(results) {
if (results.length >= 1) {
console.log("Found google.com in the new tab");
}
chrome.tabs.executeScript(results[0].id, {
code: "foo = document.getElementsByTagName('input'); console.log(foo,' This is sent from the active tab');
chrome.storage.local.set({
'foo': foo[0]
});
"},function(foo) {chrome.storage.local.get("
foo ", function(foo) {console.log(foo , 'This has retrieved foo right after saving it to storage.');});});
});
}
});
}
search()
function search(result) {
chrome.storage.local.get("foo", function(result) {
console.log(result, 'This has retrieved foo from storage in a separate function within background.js');});
}
the first console log will show the html collection object for the inputs from google.com inside the google.com tab console, the other two will show blank objects in the background.js console. Thanks, I will find another solution.
Related
I'm trying to scrape values from a page that have been generated by a JS script. When I inspect the page, i see the values there, but my selectors return null/undefined.
The purpose of the extension is to allow people, on click of a button, to scrape their personalised data from a page that requires login WITHOUT having to provide any login details to the extension.
In chrome-console, the static "title" values return, so i'm pretty sure my selectors are fine and it's just that accessing the document doesn't count for the executed scripts.
From reading, I might need to use something like pupeteer or selenium, but it seems they fire up their own browser instance (bad, as I'd need to take user login details to mock the sign in process) or i'd need to modify how the chrome browser starts with --remote-debugging-port=A_PORT_NUMBER which i want to avoid.
From chrome console and my extension, I can retrieve the values highlighted green, (so it is not an issue with iframes as some posts suggest) and can't retrieve values highlighted red.
HTML structure in image
From popup.html
document.addEventListener("DOMContentLoaded", function () {
...
document.querySelector('button[id="scrape"]').addEventListener("click", function onclick() {
chrome.tabs.query({ currentWindow: true, active: true },
function (activeTab) {
chrome.tabs.sendMessage(activeTab[0].id, { action: "putSource_scrapeSalePage", index: activeTab[0].index })
}
)
})
...
}, false)
From content.js
//Need to import pupeteer/selenium here? How else to use it for active tab?
chrome.runtime.onMessage.addListener(
function (request, sender, sendResponse) {
...
else if (request.action === "putSource_scrapeSalePage") {
let htmlvar = $(document)
console.log(htmlvar);
let test = $('td[desc= "transactionType"]').text().trim() //returns fine
let tableData1raw = $('table.tableDataOne tbody tr').find("tbody").find("tr")
let tableData1raw_almost = $(tableData1raw).each(function (i, element) {
console.log(element)
const $element = $(element).find("td")
console.log($element)
...
The Question:
If there is no better way to do this, how can I do this from content-script with something like pupeteer?
In the end I was able to use the Value i know i COULD get ("transaction type" title) and use it to traverse to it's sibling element (+) and retrieve whatever Div was there, instead of trying to target the Div class directly.
$('td[desc= "transactionType"] + td').find("div").text();
I'm trying to make a Chrome Extension that will take some of the contents of a page (The inner HTML of a <span> with id="productTitile") I then would need to take that value and put it into a field in my popup.html.
I have tried this:
document.getElementById('input_87').value = chrome.tabs.executeScript({
code: 'document.getElementById("productTitle").innerHTML'
});
But it just returns undefined into the field. I then ran document.getElementById("productTitle").innerHTML in the console in the parent page, and it gave me the expected value, but when I ran the whole code in console of the popup extension, it again returned undefined.
What am I doing wrong?
First off, as Haibara Ai says, chrome.tabs.executeScript is asynchronous - it doesn't return anything (and doesn't do anything immediately).
A good source on this in general is this question: How do I return the response from an asynchronous call? (spoiler: you can't)
If you look at the docs (which, by the way, should be your first unconditional reflex), you'll see that it has a callback and if you read the above question you'll understand it's your only option. However, there are 2 additional complications:
The callback gets an array of results. That happens because executeScript can, optionally (with allFrames: true or frameId specified), run in subframes. So you'll need to use the first element of the results array:
chrome.tabs.executeScript({
code: 'document.getElementById("productTitle").innerHTML'
}, function(results) {
document.getElementById('input_87').value = results[0];
});
The call to executeScript can fail - for example, when the page is not scriptable regardless of permissions, such as the Chrome Web Store. It's wise to check that you actually got the result before using it:
chrome.tabs.executeScript({
code: 'document.getElementById("productTitle").innerHTML'
}, function(results) {
if (chrome.runtime.lastError) {
// Couldn't execute the script at all
} else if (typeof results[0] === "undefined") {
// Couldn't find what we wanted
} else {
// Everything is fine
document.getElementById('input_87').value = results[0];
}
});
I'm developing an extension for Google Chrome and the problem I'm having is I need to be able to call a JavaScript function that belongs to the webpage that's opened in the tab.
For details, the website is my website, therefore I know that function does exist. That function does a lot of things based on a string value. I want the user to be able to highlight text on any webpage, click a button from the Chrome extension that automatically loads my webpage and calls that function with the highlighted text as it's value.
Here's what I got so far:
chrome.tabs.create({ url: "https://mywebsite.com" }, function (tab) {
var c = "initPlayer('" + request.text + "');"; ////'request.text' is the highlighted text which works
chrome.tabs.executeScript(tab.id, { code: c });
});
But Chrome console says: "Uncaught ReferenceError: initPlayer is not defined."
I know that function does exist as it is in my code on my own website.
Any help is highly appreciated. Thanks!
This happens because pages and content scripts run inside two separate javascript contexts. This means that content scripts cannot acces functions and variables inside a page directly: you'll need to inject a script into the page itself to make it work.
Here is a simple solution:
chrome.tabs.create({url: "https://mywebsite.com"}, function (tab) {
var c = "var s = document.createElement('script');\
s.textContent = \"initPlayer('" + request.text + "');\";\
document.head.appendChild(s);"
chrome.tabs.executeScript(tab.id, {code: c});
});
NOTE: since January 2021, use Manifest V3 with chrome.scripting.executeScript() instead of chrome.tabs.executeScript().
With the above code you will basically:
Create the tab
Inject the code (variable c) into it as a content script that will:
Create a script with the code you want to execute on the page
Inject the script in the page and, therefore, run its code in the page context
I'm rather new to Javascript and Crossrider. I believe what I'm trying to do is a rather simple thing - maybe I missed something here?
I am writing an extension that automatically logs you into Dropbox and at a later time will log you out. I can log the user into Dropbox automatically, but now my client wants me to automatically log those people out of dropbox by FINDING the open Dropbox windows and logging each one of them out.
He says he's seen it and it's possible.
Basically what I want is some code that allows me to get the active tabs, and set the location.href of those tabs. Or even close them. So far this is what I got:
//background.js:
appAPI.ready(function($) {
// Initiate background timer
backgroundTimer();
// Function to run backround task every minute
function backgroundTimer() {
if (appAPI.db.get('logout') == true)
{
// retrieves the array of tabs
appAPI.tabs.getAllTabs(function(allTabInfo)
{
// loop through tabs
for (var i=0; i<allTabInfo.length; i++)
{
//is this dropbox?
if (allTabInfo[i].tabUrl.indexOf('www.dropbox.com')!=-1)
{
appAPI.tabs.setActive(allTabInfo[i].tabId);
//gives me something like chrome-extension://...
window.alert(window.location.href);
//code below doesn't work
//window.location.href = 'https://www.dropbox.com/logout';
}
}
appAPI.db.set('logout',false);
});
window.alert('logged out.');
}
setTimeout(function() {
backgroundTimer();
}, 10 * 1000);
}
});
When I do appAPI.tabs.setActive(allTabInfo[i].tabId); and then window.alert(window.location.href); I get as address "chrome-extension://xxx" - which I believe is the address of my extension, which is totally not what I need, but rather the URL of the active window! More than that, I need to navigate the current window to the log out page... or at least refresh it. Can anybody help, please?
-Rowan R. J.
P.S.
Earlier I tried saving the window reference of the dropbox URL I opened, but I couldn't save the window reference into the appAPI.db, so I changed technique. Help!
In general, your use of the Crossrider APIs looks good.
The issue here is that you are trying to use window.location.href to get the address of the active tab. However, in the background scope, the window object relates to the background page/tab and and not the active tab; hence you receive the URL of the background page. [Note: Scopes can't directly interactive with each others objects]
Since your objective is to change/close the URL of the active dropbox tab, you can achieve this using messaging between scopes. So, in your example you can send a message from the background scope to the extension page scope with the request to logout. For example (and I've taken the liberty to simplify the code):
background.js:
appAPI.ready(function($) {
appAPI.setInterval(function() {
if (appAPI.db.get('logout')) {
appAPI.tabs.getAllTabs(function(allTabInfo) {
for (var i=0; i<allTabInfo.length; i++) {
if (allTabInfo[i].tabUrl.indexOf('www.dropbox.com')!=-1) {
// Send a message to all tabs using tabId as an identifier
appAPI.message.toAllTabs({
action: 'logout',
tabId: allTabInfo[i].tabId
});
}
}
appAPI.db.set('logout',false);
});
}
}, 10 * 1000);
});
extension.js:
appAPI.ready(function($) {
// Listen for messsages
appAPI.message.addListener(function(msg) {
// Logout if the tab ids match
if (msg.action === 'logout' && msg.tabId === appAPI.getTabId()) {
// change URL or close code
}
});
});
Disclaimer: I am a Crossrider employee
What I want to do is to run go() function in image.js file. I've googled around and I understand that is not possible to run inline scripts.
What is the best method to call the JavaScript I want? Events? Messages? Requests? Any other way?
Here is my code so far:
background.js
chrome.browserAction.onClicked.addListener(function(tab) {
var viewTabUrl = chrome.extension.getURL('image.html');
var newURL = "image.html";
chrome.tabs.create({
url : newURL
});
var tabs = chrome.tabs.query({}, function(tabs) {
for (var i = 0; i < tabs.length; i++) {
var tab = tabs[i];
if (tab.url == viewTabUrl) {
//here i want to call go() function from image.js
}
}
});
});
image.html
<html>
<body>
<script src="js/image.js"></script>
</body>
</html>
image.js
function go(){
alert('working!');
}
There are various ways to achieve this. Based on what exactly you are trying to achieve (which is not clear by your question), one way might be better than the other.
An easy way, would be to inject a content script and communicate with it through Message Passing, but it is not possible to inject content scripts into a page with the chrome-extension:// scheme (despite what the docs say - there is an open issue for correcting the docs).
So, here is one possibility: Use window.postMessage
E.g.:
In background.js:
var viewTabURL = chrome.extension.getURL("image.html");
var win = window.open(viewTabURL); // <-- you need to open the tab like this
// in order to be able to use `postMessage()`
function requestToInvokeGo() {
win.postMessage("Go", viewTabURL);
}
image.js:
window.addEventListener("message", function(evt) {
if (location.href.indexOf(evt.origin) !== -1) {
/* OK, I know this guy */
if (evt.data === "Go") {
/* Master says: "Go" */
alert("Went !");
}
}
});
In general, the easiest method to communicate between the background page and extension views is via direct access to the respective window objects. That way you can invoke functions or access defined properties in the other page.
Obtaining the window object of the background page from another extension page is straightforward: use chrome.extension.getBackgroundPage(), or chrome.runtime.getBackgroundPage(callback) if it's an event page.
To obtain the window object of an extension page from the background page you have at least three options:
Loop through the results of chrome.extension.getViews({type:'tab'}) to find the page you want.
Open the page in the first place using window.open, which directly returns the window object.
Make code in the extension page call a function in the background page to register itself, passing its window object as a parameter. See for instance this answer.
Once you have a reference to the window object of your page, you can call its functions directly: win.go()
As a side note, in your case you are opening an extension view, and then immediately want to invoke a function in it without passing any information from the background page. The easiest way to achieve that would be to simply make the view run the function when it loads. You just need to add the following line to the end of your image.js script:
go();
Note also that the code in your example will probably fail to find your tab, because chrome.tabs.create is asynchronous and will return before your tab is created.