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

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.

Related

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.

How to communicate with popup after reload

My page opens a popup and communicates with it using window.postMessage. To know when it is ready, one of the first things in the popup-script is:
window.opener.postMessage("initialize")
and then I reply and give the data and so on.
However if I have two instances of the same page open I'm running into a few problems. In Chrome and Firefox it actually opens individual popups even when the windowName is set. Setting focus using myPopup.focus() doesn't even seem to bring them up to the top. In IE however it reuses the same window, which I initially thought was a great improvement.
As stated in the spec, the window.opener reference is however never updated, even when another parent reopened it. Meaning the initial code will communicate with my first instance. I've tried in various ways to change the behavior, but I can't seem to communicate between these in any way.
Then I found a clever way of detecting this, storing the dimensions and position, then closing and reopening it. It looks like this:
const createPopup = (dimensions) => {
let features = "menubar=0,toolbar=0,status=0,personalbar=0,location=0,resizable=1,scrollbars=1";
const myPopup = window.open("/imageViewer.html", "ImageViewer", features);
if (myPopup.opener !== window) {
dimensions = { height: myPopup.outerHeight, width: myPopup.outerWidth, X: myPopup.screenX, Y: myPopup.screenY };
myPopup.close();
return createPopup(dimensions);
}
if (dimensions) {
myPopup.moveTo(dimensions.X, dimensions.Y);
myPopup.resizeTo(dimensions.width, dimensions.height);
} else {
myPopup.focus();
}
return myPopup;
};
The problem is that the main use I have for this is when the popup is located on a secondary monitor, and it doesn't seem to be allowed to neither move the popup outside the dimensions of the primary screen.
Is there anyway to solve this problem with using postMessage? My only other alternative which comes to mind is using localstorage to put information and look this up on intervals, but I'm not very happy with such a solution.
So to sum up I need to have a function (createPopup) which will create or bring a secondary window/popup to the front. The main window needs to be able to communicate with it, and it must work when using different instances of the same main page. (but running the function again when switching instances is OK.)
While setting in JavaScript failed, it works when setting directly when opening the window in the features-parameter:
export const createPopup = (dim) => {
let features = "menubar=0,toolbar=0,status=0,personalbar=0,location=0,resizable=1,scrollbars=1";
if (dim) {
features += ",left=" + dim.X + ",top=" + dim.Y + ",width=" + dim.width + ",height=" + dim.height;
}
const myPopup = window.open("/imageViewer.html", "ImageViewer", features);
try {
if (myPopup.opener !== window) {
throw Error("just trying to read opener will throw exception in IE if the opening window has been closed");
}
} catch {
dim = { height: myPopup.outerHeight, width: myPopup.outerWidth, X: myPopup.screenX, Y: myPopup.screenY };
myPopup.close();
return createPopup(dim);
}
myPopup.focus();
return myPopup;
};
Focus still doesn't seem to work in chrome-based browsers, but that is a very different bug indeed.

Capture screenshot of Electron window before quitting

In an Electron app, I can take a screenshot of my window from the main process using this:
let win = new BrowserWindow(/* ... */);
let capturedPicFilePath = /* where I want that saved */
win.capturePage((img) => {
fs.writeFile(capturedPicFilePath, img.toPng(), () => console.log(`Saved ${capturedPicFilePath}`))
})
Awesome. Now I'd like to do that right before app quits. Electron emits a particular event for that, that I tried to use:
Event: 'before-quit' : emitted before the application starts closing its windows.
Problem: if I use the same code as above in a handler for that event, the file is created but empty.
I'm guessing it's because the screenshot is taken in an asynchronous way, and the window is already closed when it happens.
So this does not work for me:
app.on('before-quit', (event) => {
win.capturePage(function(img) {
fs.writeFile(capturedPicFilePath, img.toPng(), () => console.log(`Saved ${capturedPicFilePath}`))
})
})
Edit 1 : Doing this in the renderer process with window.onbeforeunload fails too. It's also too late to perform the screenshot. I get this in the main console (i.e. it goes to terminal):
Attempting to call a function in a renderer window that has been closed or released.
Context: for now I'm exploring the limits of what is possible to do with screen capture (essentially for support purposes), and found that one. Not sure yet what I would do with that edge case, I thought about it not just for support, I'm also considering displaying at startup a blurred pic of previous state (some parts of my app take 1-2 seconds to load).
Any ideas?
I have had a similar problem before, I got around it by using the window close event and then preventing it from closing. Once my action had performed I then ran app.quit().
window.on('close', function (event) {
event.preventDefault();
let capturedPicFilePath = /* where you want it saved */
window.capturePage((img) => {
fs.writeFile(capturedPicFilePath, img.toPng(), () =>
console.log(`Saved ${capturedPicFilePath}`));
app.quit(); // quit once screenshot has saved.
});
});
Hope this helps!

Do we have to create new popup every time there is an update on google crome?

Do we have to create new popup if google crome updated or we can continue with the older one.
No one is sure what you're asking, but I'm going to take a stab at it. I believe you're asking if you have to use a new popup every time an advertisement is changed? If that is the case, the answer is no, you don't always have to have a new popup. HOWEVER, if the window was closed by the user, a new popup will have to be created. The following code will bring a named window to the front:
function GetAdWindow() {
// Change the window.open parameters to your liking
var AdWindow = window.open("", "AdWindow", "toolbar=no,location=no,status=no,menubar=no,scrollbars=yes,resizable=no,width=500,height=500");
return AdWindow;
}
After that, you have to determine if the page is blank, or already has an advertisement on it.
function UpdateAd(){
var AdWindow = GetAdWindow();
// If the AdWindow wasn't populated (meaning it was closed)
if (AdWindow.location.href === "about:blank") {
AdWindow.location = /*ADVERTISEMENT URL*/
} else {
// DO WHATEVER YOU WANT IF THE WINDOW HAD CONTENT
}
}
If you asking if your popup will go away if Chrome has an update, the answer is yes. Chrome as a whole will shot down and close all of the windows, then start back up clean.

Detecting when popup window is closed in Firefox

I open a new window like this:
var newWindow = window.open('myPage.aspx', null, 'height=650,width=900,status=yes,toolbar=no,scrollbars=yes,menubar=no,location=no,top=0, left=0');
And I wait for it to close:
var windowTimer = window.setInterval(function () {
if (win.closed !== false) {
//If window is closed ...
window.clearInterval(windowTimer);
}
}, 100);
This does work in Chrome and IE9 and Edge but not in Firefox, why?
Firefox does get inside the function but it never gets on win.closed if, even if there is an else it neither goes into it... is there any alternative to this?
Solution that worked for me:
On the popup window:
//Fires an event on the window that opened it
window.onbeforeunload = function () {
window.opener.myEvent();
};
On the main window:
window.myEvent= function () {
//This fires only when the popup gets closed on beforeunload
}
Note: the event to fire in the main window must be declared as public so it can be accessible, like so window.myEvent= function ()
Another reference: cross-window javascript events
Basically, I think the simplest way to do this is the following:
function hasClosed(){
document.getElementById('open').textContent = 'window has been closed';
}
window.addEventListener('beforeunload', function(){
if(window.opener) window.opener.hasClosed();
});
document.getElementById('open').addEventListener('click', function(e){
e.preventDefault();
window.open('test.html');
});
open window
Please keep in mind that SO snippets are not allowed to open windows, so the code is not functional inside the snippet. Simply copy the code and save it as a file called test.html.
You do not need to keep checking, the child will simply call the function on the parent. This has the advantage that you are not using resources to keep checking for it. Also, be aware that when navigating in this window, onbeforeunload gets called if a new page is loaded, calling the parent function, but maybe you could just do the check you were already doing in the hasClosed function.
You might want to give an uuid to your window and pass it into it so it can inform you of it's identity on closing, but that's all refinement.

Categories