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

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.

Related

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

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.

Prevent child window from closing when parent window is closed in Electron

I'm making an Electron app. This app has a way to open a new window (popup) to be dragged on a different monitor. This window has a way to open another one, and so on. I need the user to be able to close a window he does not want, but keep others open.
First, I tried window.open but the child window gets closed when the parent is closed. I thought it must be because the var gets garbage collected.
Second, I tried binding to the new-window event in the main process.
This is what I did:
let windows = [];
function createNewWindow(){
let win = new BrowserWindow(
{
width: 1600,
height: 900,
webPreferences: {
nodeIntegration: true
}
}
);
win.webContents.on('new-window', (event, url) => {
event.preventDefault()
let win = createNewWindow();
win.loadURL(url);
event.newGuest = win;
})
windows.push(win);
return win;
}
But the child gets closed when the parent is closed. I checked the windows var, but it is correctly retained in the main process, so this should not be a GC problem.
How can I open a chain of windows (with or without window.open) without them being closed when the main parent is closed?
EDIT
As I did not found a way to keep windows open, I decided to hide the windows instead of closing them. This is what I did:
win.on("close", (event) => {
if (win.hideInsteadOfClose == true) {
event.preventDefault();
win.hide();
}
});
Where hideInsteadOfClose is a property I give to new windows created. This is not the proper way of doing it, but it gets the work done. Please feel free to answer with the correct way.
As I did not found a way to keep windows open, I decided to hide the windows instead of closing them. This is what I did:
win.on("close", (event) => {
if (win.hideInsteadOfClose == true) {
event.preventDefault();
win.hide();
}
});
Where hideInsteadOfClose is a property I give to new windows created. This is not the proper way of doing it, but it gets the work done.
Please feel free to answer with the correct way.

Check if window is already open from a non-parent window (chrome extension)

I am making a Chrome extension. Clicking a button in popup.html opens a new window and loads feedback-panel.html.
This works but, on click, I'd like to check if the window is already open and if so focus to it instead of creating a new one.
JavaScript window.open only if the window does not already exist looked promissing but it relies on the open windows being stored as variables on the parent page when the window is opened and checking those before opening a new one. This wont work for me since the parent window (popup.html) will often be closed and reopened itself and I'd lose the variables.
I tried to implement the same idea but store the window variables in with chrome.storage since it lets you store objects. Well, it does let you store objects but it serializes them first so the window variable loses all of it's functions and I end up with
result.feedbackPanel.focus() is not a function
Here is my attempt:
function openFeedbackPanel(){
chrome.storage.local.get('feedbackPanel', function (result) {
console.log( result.feedbackPanel); // logs window object sans all functions
if(result.feedbackPanel && ! result.feedbackPanel.closed){
try{
result.feedbackPanel.focus();
}
catch(error){
chrome.storage.local.remove('feedbackPanel', function () {
});
createFeedbackPanel();
}
}
else{
createFeedbackPanel();
}
});
}
function createFeedbackPanel(){
var win = window.open('feedback-panel.html', 'Feedback', 'width=935, height=675');
console.log(win); // logs full object as expected
chrome.storage.local.set({"feedbackPanel": win});
}
$('#some-button').click(openFeedbackPanel());
So, since this doesnt work:
How can I check if a popup window is already open from a non-parent window (one that did not open the popup)?
no need to track windows and store them.
if you know your extension ID, the simplest way is to test all tabs url's and see if it's already opened
chrome.tabs.query({}, function(tabs) {
var doFlag = true;
for (var i=tabs.length-1; i>=0; i--) {
if (tabs[i].url === "chrome-extension://EXTENSION_ID/feedback-panel.html") {
//your popup is alive
doFlag = false;
chrome.tabs.update(tabs[i].id, {active: true}); //focus it
break;
}
}
if (doFlag) { //it didn't found anything, so create it
window.open('feedback-panel.html', 'Feedback', 'width=935, height=675');
}
});
and here is already answered how to get extension ID,
You can also use the messaging system. This is an example I use for an extension's options. Call a function like this in the onClick for the button:
// send message to the option tab to focus it.
// if not found, create it
showOptionsTab: function() {
chrome.runtime.sendMessage({window: 'highlight'}, null, function(response) {
if (!response) {
// no one listening
chrome.tabs.create({url: '../html/options.html'});
}
});
},
And in your window/tab listen for the message like this
// listen for request to make ourselves highlighted
chrome.runtime.onMessage.addListener(t.onMessage);
...
// message: highlight ourselves and tell the sender we are here
t.onMessage = function(request, sender, response) {
if (request.window === 'highlight') {
chrome.tabs.getCurrent(function(t) {
chrome.tabs.update(t.id, {'highlighted': true});
});
response(JSON.stringify({message: 'OK'}));
}
};
One advantage of this method is it does not need the tabs permission.

Chrome extension popup close fired at wrong time

I have a chrome extension which is currently consists of a background page and a popup page. There are some initialization happens when the popup is opened. I am using DOM event
doc.addEventListener('DOMContentLoaded', function() { ... }
And the behaviour is correct.
The issue is when closing the popup. Since chrome popup does not throw unload event I am using what was suggested here. Here is my code
popup.js
bgPage = chrome.extension.getBackgroundPage();
bgPageManager = bgPage.manager(); // This is the exposed api from bg page
bgPageManager.init(chrome.runtime.connect({
name: 'P1'
}));
Here connecting to the runtime and sending the port to background page so that it can listen to onDisconnect event.
Background.js
function init(port) {
port.onDisconnect.addListener(function() {
// Clean up happens here
stateManager.unregister();
});
}
This works as well.
But the issue is, this onDisconnect getting fired when the popup is getting opened, not when it is getting closed
The documentation for onDisconnect event is
An object which allows the addition and removal of listeners for a Chrome event.
Which is not every helpful ;)
So is there anything wrong I am doing, or any way when I can detect the popup close?
Unless there's something listening to chrome.runtime.onConnect in the popup page (from your comment, doesn't seem so), chrome.runtime.connect() will return a Port that immediately closes (as no-one was willing to listen).
You're definitely making it more difficult than it should be with involving getBackgroundPage though. The popup can initiate the port itself. All you need to do is:
// popup code
var bgPort = chrome.runtime.connect({name: "P1"});
// background code
var popupPort;
chrome.runtime.onConnect.addListener(function(port) {
if(port.name == "P1") {
popupPort = port;
popupPort.onDisconnect.addListener(function() {
/* Clean up happens here */
});
}
});
In case you want to preserve what you already have, the minimal code to put into the popup is this:
var bgPort;
chrome.runtime.onConnect.addListener(function(port) {
if(port.name == "P1") {
bgPort = port;
}
});
Note that in all cases you need to keep a reference to the Port object on both sides. If the garbage collector collects it, the port will be disconnected.
Finally, a port name is optional; if it's the only port you use, you can drop the code that sets/checks the name.

Close/clear a chrome extension notification while notification panel is open

References: https://developer.chrome.com/apps/notifications
I am using the chrome.notifications.create(string id, object options, function callback); to create a chrome notification.
var id = 'list';
var options = {};
options.title = 'test';
options.iconUrl = 'notification_icon.png';
options.type = 'list';
options.message = "test";
options.buttons = [{title: 'test'}];
options.items = [{title: 'test', message:'test'}];
var createCallback = function(notificationId) { console.log(notificationId); };
chrome.notifications.create(id, options, createCallback); // returns 'list';
This creates a notification as expected. All working correctly.
I then call chrome.notification.clear(string id, function callback);
var id = 'list';
var clearCallback= function(wasCleared) { console.log(wasCleared); };
chrome.notification.clear(id, clearCallback); // returns true;
This does clear the notification. All working correctly.
EXCEPT it does not clear the notification out if the notification panel is open. This is not a major problem 99% of the time. Until I implemented the button code within the notification.
Using chrome.notifications.onButtonClicked.addListener(function callback); On click I am calling the clear notification panel code, and it reports back as it has been cleared.
var onButtonClickedCallback = function (notificationId, buttonIndex) {
console.log(notificationId, buttonIndex);
if ( notificationId == 'list' ) {
chrome.notification.clear(id, clearCallback); // returns true;
}
}
chrome.notifications.onButtonClicked.addListener(onButtonClickedCallback); // onClick it returns 'list', 0
But I am looking right at it.. Once the notification panel closes and opens again, I can confirm it has actually gone. But obviously since I am clicking a button on the notification, the panel is open, but it does not clear away as I would have liked.
All this is running in an extension background without the persistence: false property (so the script is always loaded, and since I can see the output, I know the functions are being called).
Have I overlooked something? I do not see any functions that deal with closing the notification panel. So as far as I can tell, I am clearing the notification but the panel is not updating it's display.
I am using Chrome 37.0.2019.0 canary on Win8
If anyone can suggest something I may have missed, I would be greatful. My google searches reveal people having problems with the HTML notification.
This is a known bug, or rather an old design decision, with little progress.
Star the issue to raise its priority. I also suffer from the same.
Here's the workaround solution I've been using for several months now:
// open a window to take focus away from notification and there it will close automatically
function openTemporaryWindowToRemoveFocus() {
var win = window.open("about:blank", "emptyWindow", "width=1, height=1, top=-500, left=-500");
win.close();
}
chrome.notifications.clear("", function(wasCleared) {
openTemporaryWindowToRemoveFocus()
});

Categories