Electron show devtools on new window when using setWindowOpenHandler - electron

With the new electron, event 'new-window' is deprecated and replaced with BrowserContent.setWindowOpenHandler.
How to setup various events, including dom-ready for opening devtools?
app.on('ready', () => {
mainWindow = createMainWindow();
mainWindow.webContents.setWindowOpenHandler(({ url }) => {
// Set event listener on to-be-created WebContent
// { ..., overrideBrowserWindowOptions: { events, enableDevTools: true, etc } }
return { action: 'allow' };
});
});

You'd need to additionally intercept the did-create-window event which fires when the window is created:
mainWindow.webContents.on("did-create-window", (window, details) => {
window.webContents.once("dom-ready", () => window.webContents.openDevTools());
});

Related

contextmenu event not working in webview [Electron]

I'm using Electron with React+Typescript but this can be tested using just Electron+Javascript. The dom-ready event is working, I can see it in the console, but the contextmenu event is not triggered when I right click.
export default Main() {
const browserRef = useRef<WebViewProps>(null);
useEffect(() => {
const domReady = () => {
console.log("Dom is ready!");
};
const contextMenu = () => {
console.log("Context menu is working!");
};
if(browserRef.current) {
browserRef.current.addEventListener("dom-ready", domReady);
browserRef.current.addEventListener("contextmenu", contextMenu);
};
return () => {
if (browserRef.current) {
browserRef.current?.removeEventListener("dom-ready", domReady);
browserRef.current?.removeEventListener("contextmenu", contextMenu);
};
};
}, []);
return (
<webview ref={browserRef} src="https://www.google.com/" />
);
};
My contextmenu is not being called when I right click, this makes me think it doesn't fire when I right click but i don't know...

When a "open in new tab" link is clicked in a WebView element, it opens a popup window

So I am trying to stop the action of a new window opening whenever I click a link that "opens in a new tab" inside of a webview element, like this:
I would like to grab the url when the link is clicked, and prevent it from opening in a new tab.
Method 1
To stop the action of a new tab/window opening and define your own behavior in Electron, you can use the webview element's new-window event and call preventDefault() on the event object.
const { webContents } = require('electron')
webview.addEventListener('new-window', function(event) {
event.preventDefault()
const url = event.url
// Grab the URL here ...
});
This prevents the new tab/window from opening and allows you to define your own behavior such as grabbing the URL.
Method 2
You could alternatively use the webview.addEventListener method to listen for the did-start-loading event, and then use the webview.executeJavaScript method to inject a script that prevents the link from opening in a new tab/window.
...
webview.addEventListener('did-start-loading', function() {
webview.executeJavaScript(`
document.addEventListener('click', function(event) {
if (event.target.tagName === 'A' && event.target.target === '_blank') {
event.preventDefault()
// Grab the URL here ...
}
})
`)
})
This will listen for a click event for links set to _blank, and stop them from opening in a new tab/window.
I was able to fix the problem by using a combination of preload and webContents.setWindowOpenHandler
main.js
mainWindow = new BrowserWindow({ ... }) // the browserwindow code
mainWindow.webContents.on("did-attach-webview", (_, contents) => {
contents.setWindowOpenHandler((details) => {
mainWindow.webContents.send('open-url', details.url);
return { action: 'deny' }
})
})
Preload.js
const { contextBridge, ipcRenderer } = require('electron')
contextBridge.exposeInMainWorld('api', {
send: (channel, data) => ipcRenderer.invoke(channel, data),
handle: (channel, callable, event, data) => ipcRenderer.on(channel, callable(event, data))
})
Renderer.js
window.api.handle(
"open-url",
(event, data) =>
function (event, data) {
document.querySelector("webview").src = data // your webview element
},
event
);

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);

how to apply electron-context-menu in window?

I installed electron-context-menu using the command
npm i electron-context-menu
And then I used the code that is on this site
const {app, BrowserWindow} = require('electron');
const contextMenu = require('electron-context-menu');
contextMenu({
showSaveImageAs: true
});
let mainWindow;
(async () => {
await app.whenReady();
mainWindow = new BrowserWindow({
webPreferences: {
spellcheck: true
}
});
})();
But when I right click on the window, the ContextMenu with the items doesn't appear.
A white window only appears:
What should I do to make ContextMeu appear?
You won't need a lib for do this (And IMO you Shouldn't). The electron API already give to you a context-menu ready to use.
If you already use contextBridge just follow the electron site steps.
main.ts
// ...
ipcMain.on('show-context-menu', (event) => {
const template = [
{
label: 'Menu Item 1',
click: () => { event.sender.send('context-menu-command', 'menu-item-1') }
},
{ type: 'separator' },
{ label: 'Menu Item 2', type: 'checkbox', checked: true }
]
const menu = Menu.buildFromTemplate(template)
menu.popup(BrowserWindow.fromWebContents(event.sender))
})
renderer.ts
window.addEventListener('contextmenu', (e) => {
e.preventDefault()
ipcRenderer.send('show-context-menu')
})
ipcRenderer.on('context-menu-command', (e, command) => {
// What it will do when this options is click it
})
You can create a preload for each action too if you want
Also, if you want the browser default contextMenu, here comes a example.

Puppeteer in Electron: Error: Passed function is not well-serializable

Trying to come up with a GUI for Puppeteer project.
I thought about using Electron, but run into error:
Error: Passed function is not well-serializable!
when running Puppeteer functions like:
await page.waitForSelector('.modal', { visible: true });
I found a proper way to serialize when dealing with page.evaluate() but how to proceed in case of page.waitForSelector()?
Is there a work around for Puppeter's API functions to be properly serialized when required?
EDIT
I decided to rewrite
await page.waitForSelector('.modal', { visible: true });
using page.evaluate, here is the code:
// first recreate waitForSelector
const awaitSelector = async (selector) => {
return await new Promise(resolve => {
const selectorInterval = setInterval(() => {
if ($(selector).is(':visible')) {
console.log(`${selector} visible`);
resolve();
clearInterval(selectorInterval);
};
}, 1000);
});
}
and later call that function using page.evaluate():
// remember to pass over selector's name, in this case it is ".modal"
await page.evaluate('(' + awaitSelector.toString() + ')(".modal");');
Firstly context:
Generally you can not run puppeteer from browser environment, it works solely in nodejs. Electron provides 2 processes renderer and main. Whenever you want to use node you have to do it in main one.
About communication between both procesess you can read in docs, there are many ways of handling it. From what I know the best practice is to declare it in preload and use ipc bridge. Other solutions violate contextIsolation rule.
I was w wandering aound from one problem to another: like not serializable function, require not defined and many others.
Finally I rewrote everything from scratch and it works here's my solution:
main.js
const { app, BrowserWindow } = require('electron')
const path = require('path')
const { ipcMain } = require('electron');
const puppeteer = require("puppeteer");
function createWindow() {
// Create the browser window.
const mainWindow = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
preload: path.join(__dirname, 'preload.js'),
nodeIntegration: true,
contextIsolation: true,
},
})
ipcMain.handle('ping', async () => {
await checkPup()
})
async function checkPup() {
const browser = await puppeteer.launch();
const page = await browser.newPage();
await page.goto('https://example.com');
page
.waitForSelector('h1', { visible: true })
.then(() => {
console.log('got it')
});
const [button] = await page.$x("//button[contains(., 'Create account')]");
if (button) {
console.log('button: ', button)
await button.click();
await page.screenshot({ path: 'tinder.png' });
const [button] = await page.$x("//button[contains(., 'Create account')]");
if (button) {
console.log('button: ', button)
await button.click();
await page.screenshot({ path: 'tinder.png' });
}
}
await browser.close();
}
// and load the index.html of the app.
mainWindow.loadFile('index.html')
// Open the DevTools.
// mainWindow.webContents.openDevTools()
}
// This method will be called when Electron has finished
// initialization and is ready to create browser windows.
// Some APIs can only be used after this event occurs.
app.whenReady().then(() => {
createWindow()
app.on('activate', function () {
// 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 (BrowserWindow.getAllWindows().length === 0) createWindow()
})
})
// Quit when all windows are closed, except on macOS. There, it's common
// for applications and their menu bar to stay active until the user quits
// explicitly with Cmd + Q.
app.on('window-all-closed', function () {
if (process.platform !== 'darwin') app.quit()
})
// Attach listener in the main process with the given ID
ipcMain.on('request-mainprocess-action', (event, arg) => {
// Displays the object sent from the renderer process:
//{
// message: "Hi",
// someData: "Let's go"
//}
console.log(
arg
);
});
// In this file you can include the rest of your app's specific main process
// code. You can also put them in separate files and require them here.
preload.js
// All of the Node.js APIs are available in the preload process.
// It has the same sandbox as a Chrome extension.
const { contextBridge, ipcRenderer } = require('electron')
window.addEventListener('DOMContentLoaded', () => {
const replaceText = (selector, text) => {
const element = document.getElementById(selector)
if (element) element.innerText = text
}
for (const type of ['chrome', 'node', 'electron']) {
replaceText(`${type}-version`, process.versions[type])
}
})
contextBridge.exposeInMainWorld('versions', {
node: () => process.versions.node,
chrome: () => process.versions.chrome,
electron: () => process.versions.electron,
ping: () => ipcRenderer.invoke('ping'),
// we can also expose variables, not just functions
})
renderer.js
const information = document.getElementById('info')
const btn = document.getElementById('btn')
information.innerText = `This app is using Chrome (v${versions.chrome()}), Node.js (v${versions.node()}), and Electron (v${versions.electron()})`
btn.addEventListener('click', () => {
console.log('habad!!!!!')
func()
})
const func = async () => {
const response = await window.versions.ping()
information.innerText = response;
console.log(response) // prints out 'pong'
}
Sorry for a little bit of a mess I hope it will help someone maybe finding solutions to some other problems

Resources