Got some problems with the chrome.tabs.onSelectionChanged.addListener. Everything works good, till the whole window is closed. Then this listener is triggered...for whatsoever reason. For me it's kinda buggy, but anyway:
On this listener, I'm working with the chrome.tabs.get function. And this function is throwing an error in the moment of windows close:
Error during tabs.get: No tab with id: 70.
This makes sense. The tab does no longer exist in this moment. Anyone did already had a way to work around this? One possible reason would be to remove the listener on window close. But sadly, the removeListener doesnt work (if anyone knows how to remove, I'm grateful).
Best
EDIT 1.1: Modified function from serg's approach (thx for that on this way):
First i tried to only catch the tabs of the actual window with: chrome.windows.getCurrent. But this function doesn't return the windows.tabs array. So I first read out the current windows.id and only loop through the tabs of this window.
function ensureTabExists(tabId, callback) {
chrome.windows.getCurrent(function(windows) {
var exists = false;
windowsId=windows.id;
chrome.windows.getAll({populate: true}, function(windows){
loop:
for(var w=0;w<windows.length;++w) {
if (windows[w].id == windowsId) {
for(var t=0;t<windows[w].tabs.length;++t){
if(windows[w].tabs[t].id == tabId) {
exists = true;
break loop;
}
}
}
}
if(exists && callback) {
callback();
}
});
});
}
You can loop through all tabs in all windows and check if it still exists:
function ensureTabExists(tabId, callback) {
chrome.windows.getAll({populate: true}, function(windows){
var exists = false;
loop:
for(w=0;w<windows.length;w++) {
for(t=0;t<windows[w].tabs.length;t++){
if(windows[w].tabs[t].id == tabId) {
exists = true;
break loop;
}
}
}
if(exists && callback) {
callback();
}
});
}
//usage
chrome.tabs.onSelectionChanged.addListener(function(tabId, selectInfo) {
ensureTabExists(tabId, function(){
//this code will run only if tab exists
});
});
Use the chrome.window.onRemoved API to track when windows have closed. That way you can handle the window closing case more gracefully.
Related
I'm working on an extension that is supposed to extract information from the DOM based specific classes/tags,etc, then allow the user to save the information as a CSV file.
I'm getting stuck on a couple of places and haven't been able to find answers to questions similar enough.
Where I am tripped up at is:
1) Making sure that the page has completely loaded so the chrome.tabs.query doesn't return null a couple of times before the promise actually succeeds and allows the blocksF to successfully inject. I have tried placing it within a settimeout function but the chrome api doesn't seem to work within such the function.
2) Saving the extracted information so when the user moves onto a new page, the information is still there. I'm not sure if I should use the chrome.storage api call or simply save the information as an array and keep passing it through. It's just text, so I don't believe that it should take up too much space.
Then main function of the background.js is below.
let mainfunc = chrome.tabs.onUpdated.addListener(
async(id, tab) => {
if (buttonOn == true) {
let actTab = await chrome.tabs.query({
active: true,
currentWindow: true,
status: "complete"
}).catch(console.log(console.error()));
if (!actTab) {
console.log("Could not get URL. Turn extension off and on again.");
} else {
console.log("Tab information recieved.")
};
console.log(actTab);
let blocksF = chrome.scripting.executeScript({
target: { tabId: actTab[0]['id'] },
func: createBlocks
})
.catch(console.error)
if (!blocksF) {
console.log("Something went wrong.")
} else {
console.log("Buttons have been created.")
};
/*
Adds listeners and should return value of the works array if the user chose to get the information
*/
let listenersF = chrome.scripting.executeScript({
target: { tabId: actTab[0]['id'] },
func: loadListeners
})
.catch(console.error)
if (!listenersF) {
console.log("Listeners failed to load.")
} else {
console.log("Listeners loaded successfully.")
};
console.log(listenersF)
};
});
Information from the DOM is extracted through an event listener on a div/button that is added. The event listener is added within the loadListeners function.
let workArr = document.getElementById("getInfo").addEventListener("click", () => {
let domAr = Array.from(
document.querySelectorAll(<class 1>, <class 2>),
el => {
return el.textContent
}
);
let newAr = []
for (let i = 0; i < domAr.length; i++) {
if (i % 2 == 0) {
newAr.push([domAr[i], domAr[i + 1]])
}
}
newAr.forEach((work, i) => {
let table = document.getElementById('extTable');
let row = document.createElement("tr");
row.appendChild(document.createElement("td")).textContent = work[0];
row.appendChild(document.createElement("td")).textContent = work[1];
table.appendChild(row);
});
return newAr
I've been stuck on this for a couple of weeks now. Any help would be appreciated. Thank you!
There are several issues.
chrome methods return a Promise in MV3 so you need to await it or chain on it via then.
tabs.onUpdated listener's parameters are different. The second one is a change info which you can check for status instead of polling the active tab, moreover the update may happen while the tab is inactive.
catch(console.log(console.error())) doesn't do anything useful because it immediately calls these two functions so it's equivalent to catch(undefined)
Using return newArr inside a DOM event listener doesn't do anything useful because the caller of this listener is the internal DOM event dispatcher which doesn't use the returned value. Instead, your injected func should return a Promise and call resolve inside the listener when done. This requires Chrome 98 which added support for resolving Promise returned by the injected function.
chrome.tabs.onUpdated.addListener(onTabUpdated);
async function onTabUpdated(tabId, info, tab) {
if (info.status === 'complete' &&
/^https?:\/\/(www\.)?example\.com\//.test(tab.url) &&
await exec(tabId, createBlocks)) {
const [{result}] = await exec(tabId, loadListeners);
console.log(result);
// here you can save it in chrome.storage if necessary
}
}
function exec(tabId, func) {
// console.error returns `undefined` so we don't need try/catch,
// because executeScript is always an array of objects on success
return chrome.scripting.executeScript({target: {tabId}, func})
.catch(console.error);
}
function loadListeners() {
return new Promise(resolve => {
document.getElementById('getInfo').addEventListener('click', () => {
const result = [];
// ...add items to result
resolve(result);
});
});
}
I'm writing a Windows Node.js server app (using ES6 btw).
The first thing I want to do - in the top-level code - is sit in a while loop, calling an async function which searches for a particular registry key/value. This function is 'proven' - it returns the value data if found, or else throws:
async GetRegValue(): Promise<string> { ... }
I need to sit in a while loop until the registry item exists, and then grab the value data. (With a delay between retries).
I think I know how to wait for an async call to complete (one way or the other) before progressing with the rest of the start-up, but I can't figure out how to sit in a loop waiting for it to succeed.
Any advice please on how to achieve this?
(I'm fairly new to typescript, and still struggling to get my head round all async/await scenarios!)
Thanks
EDIT
Thanks guys. I know I was 'vague' about my code - I didn't want to put my real/psuedo code attempts, since they have all probably overlooked the points you can hopefully help me understand.
So I just kept it as a textual description... I'll try though:
async GetRegValue(): Promise<string> {
const val: RegistryItem = await this.GetKeyValue(this.KEY_SW, this.VAL_CONN);
return val.value
}
private async GetKeyValue(key: string, name: string): Promise<RegistryItem> {
return await new Promise((resolve, reject) => {
new this.Registry({
hive: this.Hive, key
}).get(name, (err, items) => {
if (err) {
reject(new Error('Registry get failed'));
}
else {
resolve( items );
}
});
})
.catch(err => { throw err });
}
So I want to do something like:
let keyObtained = false
let val
while (keyObtained == false)
{
// Call GetRegValue until val returned, in which case break from loop
// If exception then pause (e.g. ~100ms), then loop again
}
}
// Don't execute here till while loop has exited
// Then use 'val' for the subsequent statements
As I say, GetRegValue() works fine in other places I use it, but here I'm trying to pause further execution (and retry) until it does come back with a value
You can probably just use recursion. Here is an example on how you can keep calling the GetRegValue function until is resolves using the retryReg function below.
If the catch case is hit, it will just call GetRegValue over and over until it resolves successfully.
you should add a counter in the catch() where if you tried x amount of times you give up.
Keep in mind I mocked the whole GetRegValue function, but given what you stated this would still work for you.
let test = 0;
function GetRegValue() {
return new Promise((resolve, reject) => {
setTimeout(function() {
test++;
if (test === 4) {
return resolve({
reg: "reg value"
});
}
reject({
msg: "not ready"
});
}, 1000);
});
}
function retryReg() {
GetRegValue()
.then(registryObj => {
console.log(`got registry obj: ${JSON.stringify(registryObj)}`)
})
.catch(fail => {
console.log(`registry object is not ready: ${JSON.stringify(fail)}`);
retryReg();
});
}
retryReg();
I don't see why you need this line:
.catch(err => { throw err });
The loop condition of while isn't much use in this case, as you don't really need a state variable or expression to determine if the loop should continue:
let val;
while (true)
{
try {
val = await GetRegValue(/* args */);
break;
} catch (x) {
console.log(x); // or something better
}
await delay(100);
}
If the assignment to val succeeds, we make it to the break; statement and so we leave the loop successfully. Otherwise we jump to the catch block and log the error, wait 100 ms and try again.
It might be better to use a for loop and so set a sensible limit on how many times to retry.
Note that delay is available in an npm package of the same name. It's roughly the same as:
await new Promise(res => setTimeout(res, 100));
I'm watching multiple network folders using fs.watch(), each on a different IP. The problem is that sometimes these network drives go offline, and I need to retry watching them until they go online.
The code I wrote looks like this:
watchFolder() {
var self = this
let path = `\\\\${this.pathIP}\\folder1`
try {
fs.watch(path, (eventType, filename) => {
...
}
}
catch (err) {
//drive is offline
setTimeout ( () => {
this.watchFolder()
}, 1000);
}
}
I tried calling the same function from the catch every second, so that if it goes online it would continue normally, but apparently this takes too much memory because of the recursion. What can be an alternative way to do what I am trying to achieve?
One solution is to refactor your code into a loop, avoiding recursion, and to use the new sleep feature in JS to implement delay.
let keep_checking = true;
while (keep_checking) { // exit condition
let path = `\\\\${this.pathIP}\\folder1`
try {
fs.watch(path, (eventType, filename) => {
// Drive is online, do stuff
// ...
// Remember to set the exit condition
// eg. keep_checking = false;
}
}
catch (err) {
// Drive is offline
await sleep(1000);
}
}
This post gives a bit more details on the usage of sleep:
What is the JavaScript version of sleep()?
When I log in to my application, I am being shown a number of broadcast messages which may or may not be shown and whose number is not under my control. I need to click on a checkbox and next button to dismiss one message and move to the next. So, I need to write a loop on basis of a common element present on the messages with webdriver.io and node.js
I am new to selenium with node.js and webdriver.io and trying to write webdriver.io fucntion inside while and if loop, correct me if that is not possible
The code which I have used is below :
//Code to loop the click on elements depending upon presence of an Element
//Gherkin- I should see if Elementxyz exist then I click on Elementabc and Elementdef
//Author : Rohit
this.Then(/^I should see if "([^"]*)" exist then I click on "([^"]*)" and "([^"]*)"$/, function (selectorelement, selectorcheckbox, selectornext) {
// Element whose presence is checked
selectorelement = this.getSelector(selectorelement);
//Checkbox which needs to be ticked
selectorcheckbox = this.getSelector(selectorcheckbox);
//next button which needs to be clicked
selectornext = this.getSelector(selectornext);
return this.client
.waitForElemReady(selectornext, this.TIMEOUT_CONST)
.then(function(){
if(this.client.isExisting(selectorelement))
{
while(this.client.isExisting(selectorelement))
{
this.client
.click(selectorcheckbox)
.click(selectornext)
.pause(12000)
}
}
else{
console.log("you got lucky there are no messages this time :)")
}
}.bind(this)); });
Please help me as i am new to node.js and wedriver.io world
Hi t33n ,
i have tried the below code and loop is working fine now only thing is that my script is not waiting as .pause() is not working. Could you please help in that.
When I log in to my application, I am being shown a number of broadcast messages which may or may not be shown and whose number is not under my control. I need to click on a checkbox and next button to dismiss one message and move to the next. So, I need to write a loop on basis of a element present on the messages with webdriver.io and node.js I am new to selenium with node.js and webdriver.io and trying to write webdriver.io function for loop.
The code which i am trying to use is working and looping, only thing is giving me problem is pause which is not working as i need to pause a bit till the next message appears.
this.Then(/^I should see if "([^"])" exist then I click on "([^"])" and "([^"]*)"$/, function (selectorelement, selectorcheckbox, selectornext) {
selectorelement = this.getSelector(selectorelement);
// Checking presence of selectorcheckbox
selectorcheckbox = this.getSelector(selectorcheckbox);
selectornext = this.getSelector(selectornext);
var flag1 = false;
// function used for loop
function runNext(flag1,selectorcheckbox, selectornext,browser) {
setTimeout(function (){
browser
.isExisting(selectorcheckbox)
.then(function (isExisting) {
flag1 = isExisting;
if (flag1) {
flag1 = false;
browser.click(selectorcheckbox)
.pause(1000)
.click(selectornext)
.pause(5000); // Pause statements not working
runNext(flag1, selectorcheckbox, selectornext, browser);
}
else {
console.log("no messages left or no messages this time");
}
}, 50000)
}
.bind(this));
}
var loop= runNext(flag1,selectorcheckbox, selectornext,this.client);
});
.pause statements are only working when i am doing return this.client.pause but this stops the execution wherever i write this line.
Please help me with some solution.
maybe this working for you. If not you should see how loops can work and you can create your own loop with this.
function loop() {
client
.isVisible(selectornext).then(function(isVisible) {
if (isVisible) {
client
.isVisible(selectorelement).then(function(isVisible2) {
if (isVisible2) {
client
.click(selectorcheckbox)
.pause(1000)
.click(selectornext)
.pause(12000)
loop()
} // if (isVisible2) {
}) // .isVisible(selectorelement).then(function(isVisible2) {
} // if (isVisible) {
else{
// cancel loop here?
}
}) // .isVisible(selectornext).then(function(isVisible) {
} // function loop() {
In a Chrome extension, a script may be included as a content script or background script.
Most stuff it does is the same, but there are some would vary according to different context.
The question is, how could a script tell which context it is being run at?
Thank you.
I think this is a fairly robust version that worked in my initial tests and does not require a slower try catch, and it identifies at least the three primary contexts of a chrome extension, and should let you know if you are on the base page as well.
av = {};
av.Env = {
isChromeExt: function(){
return !!(window['chrome'] && window['chrome']['extension'])
},
getContext: function(){
var loc = window.location.href;
if(!!(window['chrome'] && window['chrome']['extension'])){
if(/^chrome/.test(loc)){
if(window == chrome.extension.getBackgroundPage()){
return 'background';
}else{
return 'extension';
}
}else if( /^https?/.test(loc) ){
return 'content';
}
}else{
return window.location.protocol.replace(':','');
}
}
};
Well I managed to work out this:
var scriptContext = function() {
try {
if (chrome.bookmarks) {
return "background";
}
else {
return "content";
}
}
catch (e) {
return "content";
}
}
It's because an exception would be thrown if the content script tries to access the chrome.* parts except chrome.extension.
Reference: http://code.google.com/chrome/extensions/content_scripts.html
The best solution I've found to this problem comes from over here.
const isBackground = () => location.protocol === 'chrome-extension:'
The background service worker at Manifest v3 does not contain a window.
I use this as part of my extension error handling which reloads the content scripts, when i receive an Extension context invalidated error:
...
if (!self.window) {
console.warn('Background error: \n', error);
} else {
location.reload();
}
...