With:
contextIsolation = true
nodeIntegration = false
What is the best way to ask for the value of a variable defined in the renderer context from preload context?
I'm developing an application for managing photo collections.
In my main.js there is a menuItem to import photos, the click function for this menu entry is:
click() {
dialog.showOpenDialog(mainWindow, {
title: 'Import',
properties: ['openFile', 'multiSelections']
}).then(result => {
if (result.filePaths.length) {
mainWindow.webContents.send('import', result.filePaths);
}
});
}
in preload.js:
ipcRenderer.on('import', (ev, origins) => {
origins.forEach(origin => {
files.importPhoto(origin, dir);
});
});
the 'dir' attribute in preload function is the destination folder to copy the photos and is the value I need to get from the app.js in the renderer.
If you want to be able to modify dir in your preload.js file, you should expose your ipcRenderer.on code as a function through the contextBridge. Ie.
preload.js
const { contextBridge, ipcRenderer } = require("electron")
contextBridge.exposeInMainWorld("api",
{
importFunc: function(dir) {
// clear out any existing listeners if this method
// is called multiple times from the app.js/renderer code
ipcRenderer.removeAllListeners("import");
ipcRenderer.on("import", (ev, origins) => {
origins.forEach(origin => {
files.importPhoto(origin, dir);
});
});
}
}
)
In your app.js/renderer code, you could call this function like so when you have the right value for dir. (Once this method is run, your MenuItem click should pull in the right value).
window.api.importFunc("C:\\myfolder");
Related
I am trying to get the selected element to the sidebar pane in my chrome extension.
It's working fine if the page has no frames when the element is in the frame, it's not working.
As per the document I have to pass the frameURL, but how do I get the frame or Iframe URL?
Thank you.
Note: This issue is duplicate that was opened in 3 years ago, but still no solution there, so re-opening it again.
In devtools.js
chrome.devtools.panels.elements.createSidebarPane(name, (panel) => {
// listen for the elements changes
function updatePanel() {
chrome.devtools.inspectedWindow.eval("parseDOM($0)", {
frameURL: // how to pass dynamic
useContentScriptContext: true
}, (result, exceptipon) => {
if (result) {
console.log(result)
}
if (exceptipon) {
console.log(exceptipon)
}
});
}
chrome.devtools.panels.elements.onSelectionChanged.addListener(updatePanel);
});
I ran into this as well. I ended up needing to add a content_script on each page/iframe and a background page to help pass messages between devtools and content scripts.
The key bit is that in the devtools page, we should ask the content_scripts to send back what their current url is. For every content script that was registered, we can then call chrome.devtools.inspectedWindow.eval("setSelectedElement($0)", { useContentScriptContext: true, frameURL: msg.iframe } );
Or in full:
chrome.devtools.panels.elements.createSidebarPane( "example", function( sidebar ) {
const port = chrome.extension.connect({ name: "example-name" });
// announce to content scripts that they should message back with their frame urls
port.postMessage( 'SIDEBAR_INIT' );
port.onMessage.addListener(function ( msg) {
if ( msg.iframe ) {
// register with the correct frame url
chrome.devtools.panels.elements.onSelectionChanged.addListener(
() => {
chrome.devtools.inspectedWindow.eval("setSelectedElement($0)", { useContentScriptContext: true, frameURL: msg.iframe } );
}
);
} else {
// otherwise assume other messages from content scripts should update the sidebar
sidebar.setObject( msg );
}
} );
}
);
Then in the content_script, we should only process the event if we notice that the last selected element ($0) is different, since each frame on the page will also handle this.
let lastElement;
function setSelectedElement( element ) {
// if the selected element is the same, let handlers in other iframe contexts handle it instead.
if ( element !== lastElement ) {
lastElement = element;
// Pass back the object we'd like to set on the sidebar
chrome.extension.sendMessage( nextSidebarObject( element ) );
}
}
There's a bit of setup, including manifest changes, so see this PR for a full example:
https://github.com/gwwar/z-context/pull/21
You can found url of the frame this way:
document.querySelectorAll('iframe')[0].src
Assuming there is at lease one iframe.
Please note, you cannot use useContentScriptContext: true, as it will make the script execute as a context page (per documentation) and it will be in a separate sandboxed environment.
I had a slightly different problem, but it might be helpful for your case too, I was dynamically inserting an iframe to a page, and then tried to eval a script in it. Here the code that worked:
let win = chrome.devtools.inspectedWindow
let code = `
(function () {
let doc = window.document
let insertFrm = doc.createElement('IFRAME')
insertFrm.src = 'about:runner'
body.appendChild(insertFrm)
})()`
win.eval(code, function (result, error) {
if (error) {
console.log('Eror in insertFrame(), result:', result)
console.error(error)
} else {
let code = `
(function () {
let doc = window.document
let sc = doc.createElement('script')
sc.src = '${chrome.runtime.getURL('views/index.js')}'
doc.head.appendChild(sc)
})()`
win.eval(code, { frameURL: 'about:bela-runner' }, function (result, error) {
if (error) {
console.log('Eror in insertFrame(), result:', result)
console.error(error)
}
})
}
})
I am creating some unit test for my component, but the test keeps failing, since the button I'm testing
keeps not getting triggerd by a click-event.
I've used the docs as a foundation for my test: https://vuetifyjs.com/sv-SE/getting-started/unit-testing/
I've also tried some of the suggestions mentioned here: https://forum.vuejs.org/t/how-to-trigger-an-onchange-event/11081/4
But it seems like I'm missing something, anyone who can help me out?
My test:
test('If you can click on the Test button', () => {
const wrapper = shallowMount(myComponent, {
localVue,
vuetify,
});
const event = jest.fn();
const button = wrapper.find({name: 'v-btn'})
expect(button.exists()).toBe(true) //this works
wrapper.vm.$on('v-btn:clicked', event)
expect(event).toHaveBeenCalledTimes(0)
button.trigger('click')
expect(event).toHaveBeenCalledTimes(1)
})
myComponent:
<template>
<v-btn class="primary-text" #click.native="methodForTesting($event)">Test</v-btn>
<template>
<script>
methods: {
methodForTesting(){
console.log('button clicked!')
}
</script>
Hope this help, I changed your HTML a bit.
Firstly, I added a <div> and put <v-btn> inside it, this is very
important.
Secondly, I declared a data prop called index which is
initialized in 1.
Thirdly, I used data-testid="button" to identify
it and find it during test.
<template>
<div>
<v-btn data-testid="button" class="primary-text" #click="methodForTesting">Test</v-btn>
</div>
</template>
<script>
export default {
data() {
return {
index: 1
};
},
methods: {
methodForTesting() {
this.index++;
}
}
};
</script>
Now, for the unit test.
The key is to use vm.$emit('click') instead of .trigger('click') since v-btn is a component of Vuetify. If you were using button tag, then you can use .trigger('click').
Also, I changed how jest finds this button.
import Vuetify from 'vuetify'
// Utilities
import { mount, createLocalVue } from '#vue/test-utils'
// Components
import Test from '#/views/Test.vue';
// VARIABLES INITIALIZATION //
const vuetify = new Vuetify()
const localVue = createLocalVue()
// TESTING SECTION //
describe('Testing v-btn component', () => {
it('should trigger methodForTesting', async () => {
const wrapper = mount(Test, {
localVue,
vuetify,
})
const button = wrapper.find('[data-testid="button"]')
expect(button.exists()).toBe(true)
expect(wrapper.vm.$data.index).toBe(1)
button.vm.$emit('click')
await wrapper.vm.$nextTick()
expect(wrapper.vm.$data.index).toBe(2)
})
})
Now, when you are doing a unit test, you should check inputs and outputs. In this case, your input is the click event and your output, is not your method been called, but the data modified or sent by this method. That's why I declared index to see if it changes when you click the button.
Anyway, if you want to check if your method was called, you can use this code instead
describe('Testing v-btn component', () => {
it('should trigger methodForTesting', async () => {
const methodForTesting = jest.fn()
const wrapper = mount(Test, {
localVue,
vuetify,
methods: {
methodForTesting
}
})
const button = wrapper.find('[data-testid="button"]')
expect(button.exists()).toBe(true)
expect(methodForTesting).toHaveBeenCalledTimes(0)
button.vm.$emit('click')
await wrapper.vm.$nextTick()
expect(methodForTesting).toHaveBeenCalledTimes(1)
})
})
But you will receive the next error:
[vue-test-utils]: overwriting methods via the `methods` property is deprecated and will be removed in the next major version. There is no clear migration path for the `methods` property - Vue does not support arbitrarily
replacement of methods, nor should VTU. To stub a complex method extract it from the component and test it in isolation. Otherwise, the suggestion is to rethink those tests.
This is my first post btw
I am creating an Electron app and I am trying to split my code in different scripts to make it more manageable; however, for some reason one of the variables in my script keeps returning undefined and I can't figure out why. I already checked similar questions here on SO, but did not find an answer.
I have a file called windowManipulation.js and this is part of it:
let signInWindow;
module.exports.createSignInWindow = () => {
signInWindow = new BrowserWindow({
show: false,
width: 1500,
height: 800,
webPreferences: {
nodeIntegration: true
}
});
signInWindow.loadFile(`views/logIn.html`)
signInWindow.once("ready-to-show", () => {
signInWindow.show();
});
signInWindow.on("close", () => {
signInWindow = null;
});
signInWindow.on('crashed', () => {
app.relaunch();
app.exit(0);
})
}
module.exports.closeSignInWindow = () => {
signInWindow.close();
signInWindow = null;
}
Now, when I call the function to create the window it creates it without a problem. But when I call the function to close it, it says that signInWindow is undefined.
Why is it undefined if it was supposed to be set when the signInWindow was created? What am I doing wrong?
It sounds like createSignInWindow and closeSignInWindow are being called from different processes. Being different processes, they each their own memory, and each would execute this file independently. So if you create the window in the main process, and close it from the window process, the window process will not think the variable exists.
So it sounds like you need to use ipcRenderer to communicate from the render to the main process so that it can close the window for you.
It'd be something like:
// renderer
const { ipcRenderer } = require('electron')
ipcRenderer.send('close-signin')
// main
const { ipcMain } = require('electron')
ipcMain.on('close-signin', closeSignInWindow)
im searching for an idea to fix my problem. First of all, there is a server.exe software, that can load some stuff. But if i change something, it needs a restart, but not, if i use a json file to store account names. Look:
const allowedPlayers =
[
"Socialclubuser1",
"Socialclubuser2",
"Socialclubuser3"
]
mp.events.add("playerJoin", (player) => {
if (!allowedPlayers.includes(player.socialClub)) {
player.notify('Youre not whitelisted!');
}
});
mp.events.add("playerJoin", (player) => {
if (!allowedPlayers.includes(player.socialClub)) {
player.kick('Youre not whitelisted!');
}
});
i would use account.json, and insert there the stuff but how?
greetings
Create an account.json file and require it using require on start.
// account.json
// ["Socialclubuser1", "Socialclubuser2", "Socialclubuser3"]
const allowedPlayers = require("./account.json");
// rest of the code
mp.events.add("playerJoin", player => {
if (allowedPlayers.indexOf(player.socialClub) === -1) {
player.notify("Youre not whitelisted!");
player.kick("Youre not whitelisted!");
}
});
I'm trying to load and render additional views async and append them to the ItemView.
Simplified code - why is $el not defined in the require() block in render() - what am I missing here? Am I not using RequireJS properly, or Marionette, or just my inexperience with javascript?
What is the recommended way of doing this? It needs to be dynamic as additional section views could be available at runtime that I don't know about yet as registered by plugins.
define(['require','marionette', 'App', 'swig', 'backbone.wreqr','text!./settings.html'],
function (require,Marionette, App,Swig, Wreqr, settingsHtml )
{
var sectionViews = ['./settingscontent/GeneralView'];
var SettingsView = Marionette.ItemView.extend(
{
template: Swig.compile(settingsHtml),
commands: new Wreqr.Commands(),
initialize: function ()
{
this.commands.addHandler('save', function (options, callback)
{
callback();
});
Marionette.ItemView.prototype.initialize.apply(this, arguments);
},
render: function()
{
Marionette.ItemView.prototype.render.call(this);
var $el = this.$el;
var self = this;
require(sectionViews, function (View)
{
$el.find('div.tab-content').append(new View(self.model).render().$el);
// $el is not defined
// self != outer this - $el is an empty div
});
return this;
}
}
return SettingsView;
})
Why are you trying to overload itemview.render?
Why not use the built in onrender event
https://github.com/marionettejs/backbone.marionette/blob/master/docs/marionette.itemview.md#render--onrender-event
from that documentation :
Backbone.Marionette.ItemView.extend({
onRender: function(){
// manipulate the `el` here. it's already
// been rendered, and is full of the view's
// HTML, ready to go.
}
});
seems easier and more typical of marionette usage
You need to bind this inside the function to the SettingsView object. Something like:
render: function()
{
Marionette.ItemView.prototype.render.call(this);
var $el = this.$el;
var self = this;
require(sectionViews, _.bind(function (View)
{
...
}, this));
return this;
}
The local variables will not be visible inside the bound function. You can use this and this.$el safely however.