Get all elements of the current window - javascript

I need to modify the DOM of a page but can not find how to get the elements of the current window.
window.onload = function() {
var bot = document.getElementById('bot');
bot.style.cursor = "pointer";
bot.addEventListener('click',function(){
chrome.tabs.query({
currentWindow: true,
active: true
}, function(tab) {
// tab[0] <----- I need to get the elements of the current window to modify
// for example document.getElementById('element')
});
});
}

I assume the code you showed runs in a popup.
To access DOM of a page open in an existing tab, you need Content Scripts.
See this Architecture Overview as the first step.
For a concrete example, given a tab ID, you can inject a script and get a single value back (as a simplest solution) like this:
chrome.tabs.executeScript({file: "content.js"}, function(result){
console.log(result);
});
And the content script:
// Simplest possible DOM operation
return document.getElementById('element').value;
For more advanced usage, see the documentation link above and Messaging documentation.

Related

Scrape values generated by script from active tab chrome extension content script

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();

How to get a javascript (emberjs) rendered HTML source in javascript

Problem: I am working on an extension in javascript which needs to be able to view the source HTML of a page after everything is rendered.
The problem is that no matter what method I use, I can only seem to retrieve the pre-rendered source. The website is using emberjs for generating the content of the page.
Example:
Site: https://www.playstation.com/en-us/explore/games/ps4-games/?console=ps4
When I right click and view source, I get the page before the content is loaded.When I right click and inspect element, I want to get the source after the content has loaded.
What I've tried:
background.js
var acceptedURLPattern = "playstation.com";
tabUpdatedCallback = function(tabID, changeInfo, tab) {
if(tab.url.indexOf(acceptedURLPattern) == -1) return;
var eventJsonScript = {
code: "console.log(\"Script Injected\"); window.addEventListener(\"load\", (event) => { " + browserString + ".runtime.sendMessage({ \"html\": document.documentElement.outerHTML });});"
};
browser.tabs.executeScript(tabID, eventJsonScript);
}
handleHTMLMessage = function(request, sender, sendResponse) {
console.log(request);
}
browser.tabs.onUpdated.addListener(tabUpdatedCallback);
browser.runtime.onMessage.addListener(handleHTMLMessage);
The above script is injecting an eventListener onto the page I want to grab the source of after it fires the "load" event which will then send a message back to background.js containing that source.
I've tried changing the documentElement to innerHTML/outerHTML as well as changing the eventListener to document.addEventListener(\"DOMContentLoaded\"), but none of these changes seemed to have any effect.
I've also tried using these: Get javascript rendered html source using phantomjs and get a browser rendered html+javascript but they are using phantomjs to load and execute the page, then return the html. In my solution, I need to be able to grab the already rendered page.
Thanks for the help in advance!
Edit #1:
I took a look at MutationObserver as mentioned by #wOxxOm and changed the eventJsonScript variable to look like this:
var eventJsonScript = {
code: "console.log(\"Script Injected\"); var mutationObserver = new MutationObserver( (mutations) => { mutations.forEach((mutation) => {if( JSON.stringify(mutation).indexOf(\"Yakuza\") != -1) { console.log(mutation); } });}); mutationObserver.observe(document.documentElement, {attributes: true, characterData: true, childList: true, subtree: true, attributeOldValue: true, characterDataOldValue: true}); mutationObserver.takeRecords()"
};
however despite the site clearly having a section for Yakuza 6, the event doesn't get fired. I did remove the if condition in the injected script to verify that events do get fired normally, it just doesn't seem to contain information that I'm looking for.
So the good news is that someone has already written the code to do this in Ember, you can find it here:
https://github.com/emberjs/ember-test-helpers/blob/031969d016fb0201fd8504ac275526f3a0ab2ecd/addon-test-support/%40ember/test-helpers/settled.js
This is the code Ember tests use to wait until everything is rendered and complete, or "settled".
The bad news is it is a nontrivial task to extract it correctly for your extension.
Basically, you will want to:
Wait till the page is loaded (window.load event)
setTimeout at least 200 ms to ensure the Ember app has booted.
Wait until settled, using code linked above.
Wait until browser is idle (requestIdleCallback in latest Chrome, or get a polyfill).
Hope this helps get you started.

Chrome extension to refresh a page every minute and run a (javascript) script every time it refreshes

What I want:
My propose is to check if new content was added in a page (that I do not own), so I was thinking to make a script that save the last content added in a cookie and refresh the page every minute: If the cookie doesn't match the last content added, that would mean there is new content and I would receive a notification.
Let's try with pseudocode:
main_file:
include: functions.js;
cookie last_content_added= get_first_paragraph();
//Refresh script
do (every_minute){
page_reload();
}
when.page.reload.complete {
run script_check_content
}
functions.js
script_check_content{
var content_check = get_first_paragraph();
if (content_check == cookie[last_content_added])
{
//do nothing
}
else{
//new content was added
play.notification.mp3
cookie[last_content.added] = get_first_paragraph();
}
}
Am I not thinking in an easier solution for what I'm looking for?
I'm new to chrome extensions, if you could separate the code in different files like it was a real extension, I would appreciate very much.
I recommend to use 'chrome.tabs.query', use this to get all tabs that have the specified properties or all tabs if no properties are specified and 'chrome.tabs.executeScript' to inject the javascript code into a page that calls 'window.location.reload(). to refresh the page.
Here's a sample code to get the current tab and reload it using chrome.tab methods:
chrome.tabs.query({active: false, currentWindow: true}, function (arrayOfTabs) {
var code = 'window.location.reload();';
chrome.tabs.executeScript(arrayOfTabs[0].id, {code: code});
});
Also, include 'onCompleted' listener to listen when it is completely loaded and initialized.
chrome.webNavigation.onCompleted.addListener(function callback).
Take a look at MutationObserver, it provides a way to react to changes in a DOM. You can provide a callback to react to DOM changes and don't need to use a timer.

Chrome extensions: best method for communicating between background page and a web site page script

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.

Chrome-Extension: iterate through all tabs?

How would I iterate through all tabs a user has open and then check if they have a particular HTML item with id = 'item'?
It appears this method has been deprecated in favor of chrome.tabs.query:
http://developer.chrome.com/extensions/tabs.html#method-query
So now you'd want to do:
chrome.tabs.query({}, function(tabs) { /* blah */ } );
Passing an empty queryInfo parameter would return all of the tabs.
You can make it like this :
chrome.tabs.getAllInWindow(null, function(tabs){
for (var i = 0; i < tabs.length; i++) {
chrome.tabs.sendRequest(tabs[i].id, { action: "xxx" });
}
});
After that to look after your item, if you can make it like this :
document.getElementById('item')
Don't forget that you can't manipulate the HTML by using the "background page" So the first code snip is for the background page, and the second have to be on a content script ;)
Edit July 2022:
This answer was originally written for MV3 users. If you're looking for a MV2 answer, see above. Otherwise, this answer will work perfectly fine with MV3.
Original answer
chrome.tabs.query is the function you're looking for. You can pass in parameters to a object to filter the tabs. In your case, you want to iterate over all the open tabs. Here's two versions of the code you're looking for:
Synchronous
chrome.tabs.query({}, function(tabs) {
tabs.forEach(function (tab) {
// do whatever you want with the tab
});
});
Asynchronous
var tabs = await chrome.tabs.query({});
tabs.forEach(function (tab) {
// do whatever you want with the tab
});
In both cases, the parameter tab is a Tab.
This is a not deprecated vanilla way (may 2019):
chrome.tabs.query({}, function(tabs){
tabs.forEach(tb => {
chrome.tabs.sendMessage(tb.id, { action: "xxx" });
});
});

Categories

Resources