How to change text in Chrome Confirm window "XYZ Website says:" - javascript

I have developed extension that generates confirm window on a current open tab after a certain interval of time. Its working fine but I want to change the text "XYZ website says" to my extension name. How can I do this? Is it possible?
Here is my background code:
var notifyTimer = setInterval(func, 5 * 1000);
console.log('setinterval ran');
function func() {
let ActiveTab = getCurrentTab();
console.log(ActiveTab)
}
const confirmWindow = () => {
let result = confirm("You've been working for too long on Chrome. Would you like to take a break?");
return result;
}
async function getCurrentTab() {
let queryOptions = { active: true, lastFocusedWindow: true };
let [tab] = await chrome.tabs.query(queryOptions);
console.log(tab.url)
//alert(" Hello!")
// SOME CODE TO GENERATE CONFIRM WINDOW or ALERT
chrome.scripting.executeScript({
target: { tabId: tab.id },
func: confirmWindow
});
return tab;
}

It's not possible to fully customise this text - since the UI is controlled by the browser it's important that it's correctly attributed, and as a result the browser is responsible for choosing this string. However, you can have your extension name appear here:
In MV2, make sure you call alert from the background page rather than a content script.
In MV3, call alert from an offscreen document. One caveat here is that offscreen documents must be created with a reason and I don't think any of the currently supported reasons are appropriate for alerts. If your alert is part of a larger operation, this could work, but otherwise this approach may not be suitable.

Related

clientScript() function in testcafe isn't working on all pages

According to Testcafe's documentation, I should be able to inject a clientScript into all pages: https://testcafe.io/documentation/402843/guides/advanced-guides/inject-client-scripts#add-client-scripts-to-all-tests
I currently have it set up to inject this script so that it can dismiss notifications that pop up in our application which overlay buttons that we need to interact with.
const notifications_div = document.querySelector('.notifications')
if (notifications_div) {
// Creates the MutationObserver and triggers the callback
const mutationObserver = new MutationObserver(() => {
// Checks to see if the style is set to 'block'
if (notifications_div.style.display == 'block') {
// Set the dismiss button to a variable each time since the previous will no longer exist.
let dismiss_button = document.querySelector("a[data-turbo-method='delete']")
// Click the dismiss button; Timeout is needed to avoid race condition errors.
setTimeout(() => { dismiss_button.click(); }, 3000);
// Hide the notifications_div again since it never truly goes away; Timeout is needed to avoid race condition errors.
setTimeout(() => { notifications_div.style.display = 'none'; }, 3000);
}
})
// Starts the observation of the notifications_div and checks for a change on 'style'
mutationObserver.observe(notifications_div, {
attributes: true,
attributeOldValue: true,
attributeFilter: ['style']
})
}
When I run this code in the console and then trigger a notification, it works just fine. When I run a testcafe suite I still end up seeing notifications (that asynchronously pop up), cover the button that I need to interact with, and never close.
When does the code actually get injected? Is it every page load?
Video of the script working fine via the console: https://www.loom.com/share/1a5b96d054a345748e4f018bc56af413
The Client Script injection should work even after a new page is loaded. The following code snippet demonstrates that the script is injected:
fixture`f`
.page`http://example.com`;
const clientScript = `
console.log('location: ' + window.location.href);
`;
test
('My test', async t => {
await t.navigateTo('http://google.com');
await t.debug();
})
.clientScripts({ content: clientScript });
If this code does not help, please create a separate issue in the TestCafe official repository using the following template and share an example where the problem is reproduced: https://github.com/DevExpress/testcafe/issues/new?assignees=&labels=TYPE%3A+bug&template=bug_report.yaml.

Guaranteeing at most 1 chrome extension popup page open at a time

On my chrome extension, I have a popup page and a background script.
As default, when I click on the extension's icon in two different windows, a popup will open in both windows.
I want to limit the amount of popups opened by the extension to be at most one at a time.
Here's how the full scenario that I'm trying to create:
At first no pop up is activated.
Window A opened a popup.
Window B opened a popup, in which case, Window A's popup will close.
Window C is created, go to 2, but this time Window A<-Window B and Window B<-Window C
If in any time The only popup that is open was closed, return to 1.
I know that a popup windows was created because I have a simple port connection that is invoked on the popup startup. Thus, the background is in theory aware of all popup windows that are created, namely, that is the code that I run in the popup to connect:
const port = chrome.runtime.connect({ name: 'popup-communications' });
I attempted to solve the problem in 3 ways, all of them failed.
Attempt 1
Hold the last popup that was connected. If a new one appears, close the old one before you save the new one. Use chrome.extension.getViews to get the new port. I imagined this would work, but rapid clicks on the extension icon (to invoke browserAction) makes this popUp state confused.
let popUp;
chrome.runtime.onConnect.addListener(function connect(port) {
if (port.name === 'popup-communications') {
// attempt 1
if (popUp) {
popUp?.close?.();
popUp = null;
console.log('removed old pop up');
}
[popUp] = chrome.extension.getViews({
type: 'popup',
});
});
Attempt 2
Close all popups that received from chrome.extension.getView, but the last one. The problem with this approach is that the chrome.extension.getView does not guarantee any order.
let popUp;
chrome.runtime.onConnect.addListener(function connect(port) {
if (port.name === 'popup-communications') {
// attempt 2
const popUps = chrome.extension.getViews({
type: 'popup',
});
console.log(popUps);
for (let i = 0; i < popUps.length - 1; i++) {
popUps[i].close();
}
});
I also experimented with chrome.browserAction.disable and chrome.browserAction.enable. This solution maintains indeed maintains 1 popup at all time, but I want it the popup to be available whenever I click on the extension icon, and this will not happen with this approach (instead I will need to find the relevant window with this popup)
Is there a way to achieve what I'm trying to do here?
I was able to achieve this behavior in the following way.
background.js
The background listens to connecting popups.
When a popup connects, it will broadcast a message to all open popups to close. Note this message is not sent over the port since the port connection does not broadcast.
There should exist a way to optimize, since at most one other popup can be open, but I will leave that for another time and prefer not to create global variables.
chrome.runtime.onConnect.addListener(function connect(port) {
if (port.name === 'popup-communications') {
port.onMessage.addListener(function (msg) {
if (msg.here) {
const activeTabId = msg.here;
// broadcast close request
chrome.runtime.sendMessage({closeUnmatched: activeTabId});
}
});
}
});
popup.js
Perform a lookup of its tab id.
Add a message listener to know when to close. If the message to close does not match current tab id, popup will close. window.close() is sufficient to close a popup.
"announce" to background that popup is ready by sending the tab Id over the port.
async function getCurrentTab() {
let queryOptions = {active: true, currentWindow: true};
let [tab] = await chrome.tabs.query(queryOptions);
return tab;
}
function addListener(myTabId) {
chrome.runtime.onMessage.addListener(function (msg) {
if (msg.closeUnmatched && msg.closeUnmatched !== myTabId) {
window.close();
}
});
}
(async function constructor() {
const port = chrome.runtime.connect({name: 'popup-communications'});
// whoami lookup
const {id: myTabId} = await getCurrentTab();
// add handler to self-close
addListener(myTabId);
// tell background I'm here
port.postMessage({here: myTabId});
// do whatever with port..
}());
I assume steps 1-3 can be done faster than user switching tabs/windows to activate another popup. The port connection was complicating things, but I left it in the answer, since you may have a use case for it.

Navigator.share only working once in iOS, second click throws error "request is not allowed by the user agent..."

Has anyone else ran into this issue? I'm implementing the Web Share API to add a share button to a number of listings on a page. The code seems to be working fine initially, as I can click any of the "Share" buttons and the dialog will appear correctly.
However, if I close the dialog and try to click it again, I get an error saying "The request is not allowed by the user agent or the platform in the current context, possibly because the user denied permission."
Stranger still, I'm experiencing this same behavior on all these tutorial sites on how to implement the Web Share API (example: https://alligator.io/js/web-share-api/ - Try clicking the "Share Me!" button halfway down the page more than once on iOS Safari.)
Here's my code for reference:
const shareButtons = document.querySelectorAll('.share');
for (const button of shareButtons) {
button.addEventListener("click", async () => {
if (navigator.share) {
try {
await navigator.share({
url: button.getAttribute('data-url')
});
} catch (err) {
alert(err.message);
}
} else {
// fallback
}
});
}
Appreciate any insight I can get on this - Thanks much
Which version of IOS are you using?
It looks like there is a known bug in IOS 14.0.1 where the initial promise does not resolve.
https://developer.apple.com/forums/thread/662629
suggested fix from the article (which worked for me) is:
navigator.share(...)
.then(() => {
// this will never happen in IOS 14.0.1
})
.catch(error => {
//this will catch the second share attempt
window.location.reload(true); // now share works again
});
or in your case with try/catch
const shareButtons = document.querySelectorAll('.share');
for (const button of shareButtons) {
button.addEventListener("click", async () => {
if (navigator.share) {
try {
await navigator.share({
url: button.getAttribute('data-url')
});
} catch (err) {
// this will catch the second share attempt on ios14
window.location.reload(true); // now share works again
alert(err.message);
}
} else {
// fallback
}
});
}
The downside is it actually means the user will have to click twice to trigger the second share if they are using IOS14 - but it won't happen to other users
EDIT: I have found a more concise solution that seems to solve this problem. It basically creates an iframe and uses the iframe's navigator to share instead of the page, that way you can hard reload the iframe every time you share to prevent it from hanging.
// create an invisible "sharing iframe" once
var sharingIframe = document.createElement("iframe");
var sharingIframeBlob = new Blob([`<!DOCTYPE html><html>`],{type:"text/html"});
sharingIframe.src = URL.createObjectURL(sharingIframeBlob);
sharingIframe.style.display = "none"; // make it so that it is hidden
document.documentElement.appendChild(sharingIframe); // add it to the DOM
// do not revoke the blob url because you will be reloading it later.
// also note that the following function must only be run after the iframe
// loads (and the iframe.contentWindow.navigator, which is what we are using)
function share(file){
sharingIframe.contentWindow.navigator.share({files:[file]}).then(()=>{
console.log("files shared");
sharingIframe.contentWindow.location.reload(true); // reload sharing iframe to fix iOS bug
}).catch((err)=>{
console.error("user cancelled share");
sharingIframe.contentWindow.location.reload(true); // reload sharing iframe to fix iOS bug
});
}
My old, messier solution from earlier:
I encountered the same annoying bug, even with IOS 15+ safari still acts weird with navigator.share. my solution was to essentially create a temporary iframe when sharing something, using the iframe's contentWindow.navigator instead of the top window, and then closing/removing the iframe to kill the reference every time so it doesn't hang.
function share(file){
// first, create temporary iframe and construct a temporary HTML file
// this serves as a sort of modal to click thru and sits on top
// of the main window until it is dismissed
var iframe = document.createElement("iframe");
var iframeText = `
<!DOCTYPE html>
<html style="position:absolute;width:100%;height:100%;background-color:rgba(0,0,0,0.5);">
<div style="position:absolute;left:50%;top:50%;transform:translate(-50%,-50%);padding:10px;background-color:rgba(255,255,255,0.5);;">
<button id="cancel">cancel</button>
<button id="download">download</button>
</div>
</html>
`;
var iframeBlob = new Blob([iframeText],{type:"text/html"});
iframe.src = URL.createObjectURL(iframeBlob);
iframe.style.all = "unset";
iframe.style.position = "fixed";
iframe.style.width = "100%";
iframe.style.height = "100%";
document.body.appendChild(iframe);
iframe.onload=()=>{
URL.revokeObjectURL(iframe.src); // delete the object URL
// select the elements in the iframe and add event handlers
// to either share the file or dismiss and close the dialogue
// (the iframe will close automatically after being dismissed
// or if the user cancels share by tapping away)
iframe.contentDocument.querySelector("#download").onclick=()=>{
iframe.contentWindow.navigator.share({files:[file]}).then(()=>{
console.log("files shared");
iframe.contentWindow.close();
iframe.remove();
}).catch((err)=>{
console.error("user cancelled share");
iframe.contentWindow.close();
iframe.remove();
});
};
iframe.contentDocument.querySelector("#cancel").onclick=()=>{
iframe.contentWindow.close();
iframe.remove();
};
};
}
Thanks for all the answers on this really useful. I've played around with it and found the best solution for my use is to not use async and await and to use a promise instead as the share returns multiple times.
root.navigator.share({ title, url })
.then(() => {
/*
this will be hit even if the message is logged
due to iOS being a bit rubbish ATM
*/
})
.catch((err) => {
const { message = '' } = err || {};
if (message.indexOf('user denied permission') !== -1) {
console.log(message);
}
});

getWindowHandle() Selenium Webdriver Javascript

Made some changes based on help from engineering. Here is the final code I used for grabbing the new window handle:
localdriver = #driver
#driver.getAllWindowHandles()
.then (handles) ->
localdriver.switchTo().window(handles[1])
I'm currently running an automation stack that uses Selenium Webdriver, Mocha, Chai, and Grunt. I'm creating scripts in Coffeescript, but an answer to my question in Javascript would be perfectly fine.
What I'm trying to do:
Click button on main browser window
Switch driver to the second window that opens after button click
Perform actions in the second window
Close second window and return to the first.
I've scoured the internet looking for an answer on how to do this. Just started learning all this stuff a few months ago, and I'm still stumbling through creating stuff. I'm seeing a lot of Java and C+ examples, but not much on the Javascript side. Can anyone provide an example of how to set up the code for the above scenario using Selenium Webdriver and Javascript?
var parent = driver.getWindowHandle();
var windows = driver.getAllWindowHandles();
driver.switchTo().window(windows[1]);
// do some stuff
driver.close();
driver.switchTo().window(parent);
What you want is driver.getAllWindowHandles(), but because this returns a promise, make sure that you then use the handles inside of the then function
// select the newly opened window
driver.getAllWindowHandles().then(function gotWindowHandles(allhandles) {
driver.switchTo().window(allhandles[allhandles.length - 1]);
});
Whenever new tab opens, it takes some time to come up and render. In this situation, it is difficult to switch the tab because the tab is not opened yet and driver.getAllWindowHandles() will not give handler for that tab. I solved this problem in this way, I am assuming I have one opened tab and on some button click, I am opening new 2nd tab.
function openNewTab(driver) {
driver.wait(function () {
return driver.getAllWindowHandles().then(function (handles) {
var isHandleCount2 = (handles.length == 2);
if (isHandleCount2) {
driver.switchTo().window(handles[1]);
}
return isHandleCount2;
});
}).then(function () {
// Now do some stuff in new tab
var buttonElement = driver.wait(until.elementLocated(By.xpath("//td[*//span[text()='Click me']]")));
buttonElement.click();
});
}
This code will wait until the number of handles or tabs will not equal to 2.
#Jai Prak's answer is brilliant.What about the case of three tabs or more? The newest tab will always be the last Tab.
return await driver.wait(async function () {
return await driver.getAllWindowHandles().then(async function (handles) {
// var isHandleCount2 = (handles.length == 2);
if (handles.length > 1) {
return driver.switchTo().window(handles[handles.length - 1]);
}
return false;
});
}).then(function () {
// Now do some stuff in new tab
});
The above will apply except in cases you switch between Tabs.To move the next tab, get the current Tab's index -1

Why "Prevent this page from creating additional dialogs" appears in the alert box?

In my Rails 3 application I do:
render :js => "alert(\"Error!\\nEmpty message sent.\");" if ...
Sometimes, below this error message (in the same alert box) I see: "Prevent this page from creating additional dialogs" and a checkbox.
What does this mean ?
Is that possible not to display this additional text and checkbox ?
I use Firefox 4.
It's a browser feature to stop websites that show annoying alert boxes over and over again.
As a web developer, you can't disable it.
What does this mean ?
This is a security measure on the browser's end to prevent a page from freezing the browser (or the current page) by showing modal (alert / confirm) messages in an infinite loop. See e.g. here for Firefox.
You can not turn this off. The only way around it is to use custom dialogs like JQuery UI's dialogs.
You can create a custom alert box using java script, below code will override default alert function
window.alert = function(message) { $(document.createElement('div'))
.attr({
title: 'Alert',
'class': 'alert'
})
.html(message)
.dialog({
buttons: {
OK: function() {
$(this).dialog('close');
}
},
close: function() {
$(this).remove();
},
modal: true,
resizable: false,
width: 'auto'
});
};
Using JQuery UI's dialogs is not always a solution. As far as I know alert and confirm is the only way to stop the execution of a script at a certain point. As a workaround we can provide a mechanism to let the user know that an application needs to call alert and confirm. This can be done like this for example (where showError uses a jQuery dialog or some other means to communicate with the user):
var f_confirm;
function setConfirm() {
f_confirm = confirm;
confirm = function(s) {
try {
return f_confirm(s);
} catch(e) {
showError("Please do not check 'Prevent this page from creating additional dialogs'");
}
return false;
};
};
I designed this function to hopefully circumvent the checkbox in my web apps.
It blocks all functionality on the page while executing (assuming fewer than three seconds has passed since the user closed the last dialog), but I prefer it to a recursive or setTimeout function since I don't have to code for the possibility of something else being clicked or triggered while waiting for the dialog to appear.
I require it most when displaying errors/prompts/confirms on reports that are already contained within Modalbox. I could add a div for additional dialogs, but that just seems too messy and unnecessary if built-in dialogs can be used.
Note that this would probably break if dom.successive_dialog_time_limit is changed to a value greater than 3, nor do I know if Chrome has the the same default as Firefox. But at least it's an option.
Also, if anyone can improve upon it, please do!
// note that these should not be in the global namespace
var dlgRslt,
lastTimeDialogClosed = 0;
function dialog(msg) {
var defaultValue,
lenIsThree,
type;
while (lastTimeDialogClosed && new Date() - lastTimeDialogClosed < 3001) {
// timer
}
lenIsThree = 3 === arguments.length;
type = lenIsThree ? arguments[2] : (arguments[1] || alert);
defaultValue = lenIsThree && type === prompt ? arguments[1] : '';
// store result of confirm() or prompt()
dlgRslt = type(msg, defaultValue);
lastTimeDialogClosed = new Date();
}
usage:
dialog('This is an alert.');
dialog( 'This is a prompt', prompt );
dialog('You entered ' + dlgRslt);
dialog( 'Is this a prompt?', 'maybe', prompt );
dialog('You entered ' + dlgRslt);
dialog( 'OK/Cancel?', confirm );
if (dlgRslt) {
// code if true
}
This is a browser feature.
If you could, try to employ http://bootboxjs.com/, whit this library you can do the same of
alert("Empty message sent");
by writing:
bootbox.alert("Empty message sent", function(result) {
// do something whit result
});
You'll get a nice user interface too!

Categories