BrowserView rendering below fold of BrowserWindow - electron

Running into a really strange quirk and can't figure out what I'm doing wrong:
init.js
const os = require('os');
const path = require('path');
const { app, BrowserView, BrowserWindow, ipcMain } = require('electron');
let win = null;
// Init function
const init = function() {
return new Promise((resolve, reject) => {
try {
// BrowserWindow config
const config = {
backgroundColor: '#1d1e20',
show: false,
webPreferences: {
contextIsolation: true,
enableRemoteModule: false,
preload: path.join(__dirname, 'preload.js'),
}
};
// Make BrowserWindow frameless (win) or hide titlebar (mac)
if ('win32' === os.platform()) {
config.frame = false;
} else if ('darwin' === os.platform()) {
config.titleBarStyle = 'hidden';
}
// Create BrowserWindow
win = new BrowserWindow(config);
// Add listener for BrowserWindow 'ready-to-show' event
win.once('ready-to-show', () => {
// Set traffic light position (mac)
win.setTrafficLightPosition({ x: 10, y: 27 });
// Show browser window
win.show();
// Open DevTools
win.openDevTools({ mode: "detach" });
resolve();
});
// Load app html
win.loadFile('./app.html').then(() => {
// loaded
}).catch((err) => {
reject(err);
});
} catch (err) {
reject(err);
}
});
};
// Add browser view
const addView = function(url) {
return new Promise((resolve, reject) => {
try {
// Get window size
const bounds = win.getSize();
// Create BrowserView
const view = new BrowserView({
backgroundColor: "#ffffff",
webPreferences: {
contextIsolation: true,
enableRemoteModule: false,
//preload: path.join(__dirname, 'preload.js'),
}
});
view.setBounds({
height: 375,
width: 375,
x: 0,
// Set "y" coordinate to 0
// (should be relative to BrowserWindow 0, 0)
y: 0
// Set "y" coordinate to a
// negative integer
//y: -200
// Instead, set "y" to inverse of
// BrowserWindow height
//y: bounds[1] * -1
});
// Load file
view.webContents.loadFile('./new.html').then(() => {
// Add to BrowserWindow
win.addBrowserView(view);
// Open DevTools
view.webContents.openDevTools({ mode: "detach" });
resolve();
}).catch((err) => {
reject(err);
});
} catch (err) {
reject(err);
}
});
};
// init when ready
app.whenReady().then(() => {
init().then(() => {
addView().then(() => {
console.log("Everything should be working right");
}).catch((err) => {
console.log("Error loading BrowserView");
console.error(err);
});
}).catch((err) => {
console.log("Error loading BrowserWindow");
console.error(err);
});
});
Expected result: new BrowserView stacked squarely on top of BrowserWindow at defined width and height, positioned at 0, 0.
Actual result: new BrowserView displayed below the fold, with its "y" coordinate relative to bottom of BrowserWindow. To get BrowserView to show at BrowserWindow 0, 0, must set BrowserView "y" coordinate to (BrowserWindowHeight * -1)
Edit: P.S.
package.json
{
"name": "test",
"version": "1.0.0",
"description": "",
"main": "init.js",
"scripts": {
"start": "electron ."
},
"author": "",
"license": "",
"devDependencies": {
"electron": "^12.0.1"
}
}
preload.js
(no contents)
app.html
(no contents)
app.js
(no contents)
app.css
(no contents)
new.html
<!DOCTYPE html>
<html lang="en">
<head>
<title>Test</title>
<style>
html {
margin: 0;
padding: 0;
}
body {
background: #ffffff;
margin: 0;
padding: 0;
}
</style>
</head>
<body>
<h1>Hello World!</h1>
</body>
</html>
Update #1
Delay call to view.setBounds() by moving inside promise callback of view.loadURL(). No change.
Update #2
Further delay call to view.setBounds() by additionally wrapping in setTimeout() with delay of 1000ms. No change.
Update #3
By adding the following, the BrowserView does snap to correct 0, 0 position within parent BrowserWindow, but only upon resize event:
win.on('will-resize', (event, newBounds) => {
win.getBrowserViews().forEach((view) => {
view.setBounds({
height: newBounds.height,
width: newBounds.width,
x: 0,
y: 0
});
});
});
Update #4
Replaced BrowserWindow.loadFile() and BrowserView.loadFile() calls with .loadURL() using google.com and stackoverflow.com, respectively, to eliminate the possibility of it being something to do with my local files. No change.
Update #5
By modifying the init.js as follows, it works as expected, however it's not pretty as it "snaps" into position. (Edit: to clarify, BrowserView defined with solid background which I expect to be displayed until .loadURL() completes, unless I've somehow misunderstood the purpose of the backgroundColor property) (Edit: I've just realized that backgroundColor is not a valid property of the BrowserView)
const os = require('os');
const path = require('path');
const { app, BrowserView, BrowserWindow, ipcMain } = require('electron');
let win = null;
// Init function
const init = function() {
return new Promise((resolve, reject) => {
try {
// BrowserWindow config
const config = {
backgroundColor: '#1d1e20',
show: false,
webPreferences: {
contextIsolation: true,
enableRemoteModule: false,
preload: path.join(__dirname, 'preload.js'),
}
};
// Make BrowserWindow frameless (win) or hide titlebar (mac)
if ('win32' === os.platform()) {
config.frame = false;
} else if ('darwin' === os.platform()) {
config.titleBarStyle = 'hidden';
}
// Create BrowserWindow
win = new BrowserWindow(config);
// Add listener for BrowserWindow 'ready-to-show' event
win.once('ready-to-show', () => {
// Set traffic light position (mac)
win.setTrafficLightPosition({ x: 10, y: 27 });
// Show browser window
win.show();
// Open DevTools
win.openDevTools({ mode: "detach" });
});
// Add listener for BrowserWindow 'show' event
win.once('show', () => {
resolve();
});
win.on('will-resize', (event, newBounds) => {
win.getBrowserViews().forEach((view) => {
view.setBounds({ height: newBounds.height, width: newBounds.width, x: 0, y: 0 });
});
});
// Load app html
win.loadURL("https://google.com").then(() => {
// loaded
}).catch((err) => {
reject(err);
});
} catch (err) {
reject(err);
}
});
};
// Add browser view
const addView = function(url) {
return new Promise((resolve, reject) => {
try {
// Create BrowserView
const view = new BrowserView({
backgroundColor: "#edeef0",
webPreferences: {
contextIsolation: true,
enableRemoteModule: false
}
});
// Load file
view.webContents.loadURL('https://stackoverflow.com').then(() => {
setTimeout(() => {
// Get window size
const bounds = win.getSize();
// Set BrowserView bounds
view.setBounds({ height: bounds[1], width: bounds[0], x: 0, y: 0 });
}, 1);
// Add to BrowserWindow
win.addBrowserView(view);
// Open DevTools
view.webContents.openDevTools({ mode: "detach" });
resolve();
}).catch((err) => {
reject(err);
});
} catch (err) {
reject(err);
}
});
};
// init when ready
app.whenReady().then(() => {
init().then(() => {
addView().then(() => {
console.log("Everything should be working right");
}).catch((err) => {
console.log("Error loading BrowserView");
console.error(err);
});
}).catch((err) => {
console.log("Error loading BrowserWindow");
console.error(err);
});
});
Update #6
I've come to the conclusion that in current version of Electron (12.0.1), a BrowserView is rendered with equivalent of CSS position: relative, but almost immediately thereafter, changed to equivalent of CSS position: absolute. Calling BrowserView.setBounds({ x: 0, y: 0 }) immediately upon creation of the BrowserView, the BrowserView is actually displayed at { x: 0, y: (BrowserWindow height) }, whereas calling BrowserView.setBounds({ x: 0, y: 0 }) within a setTimeout() after a 1ms delay, the BrowserView is actually displayed at { x: 0, y: 0}. I'm not sure why this is, but it seems the fix (tested only on macOS 10.14.6) is as follows:
const view = new BrowserView(...);
const size = win.getSize();
view.setBounds({ height: size[1], width: size[0], x: 0, y: size[0] * -1 });
and, on the BrowserWindow:
win.on('will-resize', (event, newBounds) => {
win.getBrowserViews().forEach((view) => {
view.setBounds({ height: newBounds.height, width: newBounds.width, x: 0, y: 0 });
});
});
Update #7
This still feels very unwieldy and I suspect this is not the intended behavior and I still may be doing something wrong. I'm going to leave this question open and hope that someone has a better solution than what I've found.
Update #8
Finally figured out that it was my sloppy implementation. After RTFM, adding some promises, and doing things in the proper order, there is no problem.
For educational purposes, I broke it down and reversed my steps to find the source of the problem so I know for future reference. For anyone else who may ever find this and have the same problem -- you must call BrowserWindow.addBrowserView() BEFORE calling BrowserView.setBounds(). That was the problem.
I was calling BrowserView.setBounds() immediately after creating the BrowserView -- at which point it was not added to the BrowserWindow yet, hence why the coords were off. I didn't call BrowserWindow.addBrowserView() until BrowserView.loadFile() resolved.

Must call BrowserWindow.addBrowserView() BEFORE calling BrowserView.setBounds() -- (see Update #8)

Related

Electron build failed to load index.html

I'm trying to build my electron application with electron-builder, I have successfully built front-end which was react and did also pass homepage: "./" in my package.json. I have also used hashbrowser as it was mentioned here
but still when I build my app, I get this message in console with white screen:
Not allowed to load local resource. I have passed webSecurity: false in webPreferences electron, It made error go away but didn't fix the problem and still getting white page.
this is my electron index.ts:
let mainWindow: BrowserWindow;
const createWidnow = () => {
mainWindowFunctions();
mainWindow = new BrowserWindow({
minHeight: 600,
minWidth: 800,
x: appXAndY.x,
y: appXAndY.y,
width: appWidthAndHeight.width,
height: appWidthAndHeight.height,
webPreferences: {
nodeIntegration: true,
contextIsolation: false,
preload: path.join(__dirname, "preload.js"),web
},
autoHideMenuBar: true,
});
mainWindow.loadURL(
isDev ? "http://localhost:3000/" : `file://${__dirname}/../build/index.html`
);
if (isDev) {
mainWindow.webContents.openDevTools();
}
}
app.whenReady().then(async () => {
createWidnow();
app.on("activate", () => {
if (BrowserWindow.getAllWindows().length === 0) {
createWidnow();
}
});
});
app.on("window-all-closed", () => {
if (process.platform !== "darwin") {
db.close();
app.quit();
}
});
const mainWindowFunctions = () => {
const files = glob.sync(
path.join(__dirname, "./controllers/**/*.js").split(path.sep).join("/")
);
files.forEach((file) => {
require(file);
});
};
I tried webSecurity false but didn't help
The problem is with Electron Packager. Just use Electron Builder. Then you can get the installer using wix3.

How to make a splash screen on Electron?

I'm new to Electron and I joined an existing project. I'm trying to add an image before the main screen of the app that will last 5 seconds. At first, I tried it with useState and setTimeone but I've realized I'm in the wrong place of the code.
After reading all kinds of solutions, and running the app - I now need your help.
This is some of the code in my main.ts file:
mainWindow = new BrowserWindow({
show: false,
width: 521,
height: 712,
icon: getAssetPath('icon.png'),
webPreferences: {
nodeIntegration: true,
contextIsolation: true,
preload: app.isPackaged
? path.join(__dirname, 'preload.+212 662-122618 js')
: path.join(__dirname, '../../.erb/dll/preload.js'),
},
});
mainWindow.loadURL(resolveHtmlPath('index.html'));
mainWindow.on('ready-to-show', () => {
if (!mainWindow) {
throw new Error('"mainWindow" is not defined');
}
if (process.env.START_MINIMIZED) {
mainWindow.minimize();
} else {
mainWindow.show();
}
});
mainWindow.on('closed', () => {
mainWindow = null;
});
const menuBuilder = new MenuBuilder(mainWindow);
menuBuilder.buildMenu();
// Open urls in the user's browser
mainWindow.webContents.setWindowOpenHandler((edata) => {
shell.openExternal(edata.url);
return { action: 'deny' };
});
// Remove this if your app does not use auto updates
// eslint-disable-next-line
new AppUpdater();
};
/**
* Add event listeners...
*/
app.on('window-all-closed', () => {
// Respect the OSX convention of having the application in memory even
// after all windows have been closed
if (process.platform !== 'darwin') {
app.quit();
}
});
app
.whenReady()
.then(() => {
createWindow();
app.on('activate', () => {
// On macOS it's common to re-create a window in the app when the
// dock icon is clicked and there are no other windows open.
if (mainWindow === null) createWindow();
});
})
.catch(console.log);

Electron how to redirect users between various .html files

I am looking to try to redirect users from "home.html" to "dashboard.html". I've searched far and wide, yet I haven't found an answer.
When creating your window with the BrowserWindow module, you would have used the method win.loadFile(filePath[, options]) to load your html file.
Once the logic to change pages is successful, all you need to do is execute the win.loadFile(filePath[, options]) line of code again with the path to your new file.
As win.loadFile(filePath[, options]) returns a promise, let's add a .then() statement as well (though not requried).
main.js (main thread)
const electronApp = require('electron').app;
const electronBrowserWindow = require('electron').BrowserWindow;
const nodePath = require("path");
let window;
function createWindow() {
const window = new electronBrowserWindow({
x: 0,
y: 0,
width: 800,
height: 600,
show: false,
webPreferences: {
nodeIntegration: false,
contextIsolation: true,
preload: nodePath.join(__dirname, 'preload.js')
}
});
window.loadFile('home.html')
.then(() => { window.show(); });
return window;
}
electronApp.on('ready', () => {
window = createWindow();
});
// Run when logic to change to dashboard is successful.
function showDashboard() {
window.loadFile('dashboard.html')
.then(() => { window.show(); })
}

Electron JS open window when click HTML Button

I have googled it and it seems like a recurrent task , but I did not find a good solution , Here is my code .
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>Hello World!</title>
</head>
<body>
<h1 style="color: red">Todos</h1>
<button type="button">Todos panel</button>
<script>
const btn = document.querySelector('button');
button.addEventListener('click', () => {
newtaskwindow();
});
function newtaskwindow() {
const remote = require('electron').remote;
const BrowserWindow = remote.BrowserWindow;
const win = new BrowserWindow({
height: 600,
width: 800
});
win.loadFile('todos.html');
}
</script>
</body>
</html>
This does not work .. I want to click the button to open a new child window.
const { app, BrowserWindow, Menu } = require('electron');
function createWindow() {
const win = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
nodeIntegration: true
}
});
win.loadFile('index.html');
}
const todoWin = () => {
const win = new BrowserWindow({
width: 500,
height: 400,
modal: true,
parent: ''
});
win.loadFile('todos.html');
};
// Menu
const isMac = process.platform === 'darwin';
const template = [
...(isMac
? [
{
label: 'Demo',
submenu: [{ role: 'about' }]
}
]
: []),
{
label: 'File',
submenu: [isMac ? { role: 'close' } : { role: 'quit' }]
}
];
const menu = Menu.buildFromTemplate(template);
Menu.setApplicationMenu(menu);
app.whenReady().then(() => {
createWindow();
});
app.on('window-all-closed', function () {
if (process.platform !== 'darwin') app.quit();
});
I see two things...
In main.js add enableRemoteModule and contextIsolation
function createWindow() {
const win = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
nodeIntegration: true,
enableRemoteModule: true,
contextIsolation:false
}
});
win.loadFile('index.html');
}
in index.html you use button.addEventListener instead of btn.addEventListener. Change it.
<script>
const btn = document.querySelector('button');
btn.addEventListener('click', () => {
newtaskwindow();
});
function newtaskwindow() {
const remote = require('electron').remote;
const BrowserWindow = remote.BrowserWindow;
const win = new BrowserWindow({
height: 600,
width: 800
});
win.loadFile('todos.html');
}
</script>
That way should work.
WARNING
As stated in Electron's documentation remote module is deprecated. You are encouraged to use IPC in order to execute code on main on request of renderer.
document.querySelector('form').addEventListener('submit', (e) => {
e.preventDefault();
const { ipcRenderer } = require('electron');
const value = document.querySelector('input').value;
ipcRenderer.send('todo:save', value);
window.close();
});
I did it through ipcRenderer

Why did electron not create file?

I'm building an app on electron and now I'm trying to create a simple file.
Here's the JS:
const app = require("electron").remote;
var dialog = app.dialog;
var fs = require("fs");
document.getElementById('save_project').onclick=() => {
dialog.showSaveDialog((filename) => {
if(filename === undefined){
console.log("You didnt save the file");
return;
};
var content = "hello there";
fs.writeFile(filename, content, (err) => {
if(err) console.log(err);
alert("The file has been successfully saved.")
})
});
};
This window will open as supposed:
Then, I wrote for instance: "hello.txt" on the name input and clicked save.
There's no log written on the console neither a file written in the directory
Edit:
with this js, happens the same ():
const fs = require("fs");
const {dialog} = require("electron").remote;
document.getElementById("save_project").addEventListener("click", () => {
dialog.showSaveDialog((filename) => {
if(filename === undefined){
console.log("nop");
return;
}
fs.writeFile(filename, content, (err) => {
if(err){
console.log(err);
return;
}
alert("file created");
});
});
}, false);
Edited:
Here's the createWindow()
function createWindow () {
// Create the browser window.
const mainWindow = new BrowserWindow({
width: 1920,
height: 1080,
webPreferences: {
preload: path.join(__dirname, "preload.js"),
nodeIntegration: true
},
});
const childWindow = new BrowserWindow ({ width: 1600, height: 800, parent: mainWindow, modal: true, show : false});
// and load the index.html of the app.
mainWindow.loadFile("index.html");
childWindow.loadFile("welcome.html");
childWindow.once("ready-to-show", () => {
childWindow.show();
});
// Open the DevTools.
mainWindow.webContents.openDevTools();
}
Basically, from what i understood, "dialog.showSaveDialog((filename).." this was kinda blocking... i fixed by using:
const {dialog} = require("electron").remote;
let filename = dialog.showSaveDialogSync()
if(filename === undefined){
console.log("filename undefined");
return;
}else{
console.log(filename)
saveAsToFile(filename, ...);
}

Resources