"Uncaught ReferenceError: window is not defined" p5.js web worker - javascript

I have a javascript code where I use the web worker with the p5.js library. it wouldn't allow me to use any of p5's functions so I have to use the importScripts("p5.js") function to import the p5.js library before using any of p5's functions.
onmessage = (e)=>{
importScripts("p5.min.js")
// other scripts
}
But even then it gives me another error that said "Uncaught ReferenceError: window is not defined". I tracked it down and it seemed that p5 is unable to use the global variable named "window". I searched around the internet for a solution but so far found none. I wonder if there is a way around this. Thank you.

The issue here is that web workers run in a very isolated context where many of the standard global variables that would exist for javascript running on a website (window, document, etc) don't exist, and unfortunately p5.js cannot load without these variables. You could try shimming them with fake versions. Here's a basic example:
let loadHandlers = [];
window = {
performance: performance,
document: {
hasFocus: () => true,
createElementNS: (ns, elem) => {
console.warn(`p5.js tryied to created a DOM element '${ns}:${elem}`);
// Web Workers don't have a DOM
return {};
}
},
screen: {},
addEventListener: (e, handler) => {
if (e === "load") {
loadHandlers.push(handler);
} else {
console.warn(`p5.js tried to added an event listener for '${e}'`);
}
},
removeEventListener: () => {},
location: {
href: "about:blank",
origin: "null",
protocol: "about:",
host: "",
hostname: "",
port: "",
pathname: "blank",
search: "",
hash: ""
}
};
document = window.document;
screen = window.screen;
// Without a setup function p5.js will not declare global functions
window.setup = () => {
window.noCanvas();
window.noLoop();
};
importScripts("/p5.js");
// Initialize p5.js
for (const handler of loadHandlers) {
handler();
}
postMessage({ color: "green" });
onmessage = msg => {
if (msg.data === "getRandomColor") {
// p5.js places all of its global declarations on window
postMessage({
color: window.random([
"red",
"limegreen",
"blue",
"magenta",
"yellow",
"cyan"
])
});
}
};
This is only going to work for a limited subset of p5.js functions. Any functions that draw to the canvas are definitely not going to work. And I would be cautious about trying to pass objects back and forth (i.e. p5.Vector, p5.Color, etc) because everything sent via postMessage gets serialized and deserialized.
I've posted a working version of this example on Glitch.

Related

Nuxt plugin cannot access Vue's 'this' instance in function blocks

So I have managed to inject hls.js to work with nuxtjs $root element and this
I did so doing it like this (hls.client.js):
import Hls from 'hls.js';
export default (context, inject) => {
inject('myhls', Hls)
}
and nuxt.config.js
plugins: [
'~plugins/hls.client.js',
],
This works great, literally :-) but I can't reference 'this' in the hls events.
playHls() {
this.playing = true
if(this.$myhls.isSupported()) {
this.hls = new this.$myhls();
this.audio = new Audio('');
this.hls.attachMedia(this.audio);
this.hls.loadSource(this.scr_arr[2]);
this.audio.play()
// 'THIS' DOES NOT WORK INSIDE this.hls.on
this.hls.on(this.$myhls.Events.MEDIA_ATTACHED, function () {
console.log(this.scr_arr[2]); // DOES NOT LOG
console.log("ok") // works great
});
// 'THIS' DOES NOT WORK INSIDE this.hls.on
this.hls.on(this.$myhls.Events.MANIFEST_PARSED, function (event, data) {
console.log('manifest loaded, found ' + data.levels.length + ' quality level') // WORKS
console.log("ok") // WORKS
this.audio.volume = 1 // DOES not work
});
}
},
So I in these Events I can't use nuxtjs 'this', cause there seems to be a different scope?
Can I somehow get 'this' nuxt scope inside these Events?
Replace your functions like
this.hls.on(this.$myhls.Events.MANIFEST_PARSED, function (event, data) {
into
this.hls.on(this.$myhls.Events.MANIFEST_PARSED, (event, data) => {
to keep the this context tied to the Vue app, otherwise it will be scoped to the context of the block scope.

Stubbing of "watchPosition" in cypress

I try to inject position changes to an JS/REACT-Application. The Application registering at window.navigator.geolocation.watchPosition. My idea is to stub the "watchPosition" method to get a handle on the callback function. Then calling the callback function from the application directly.
Like:
const watchPositionFake = (successCallback, errorCallback, options) => {
console.debug("PROXY set callback watchPosition");
originalWatchPositionSuccessCallback = successCallback;
};
cy.visit("/", {
onBeforeLoad(win) {
cy.stub(win.navigator.geolocation, "watchPosition").callsFake(watchPositionFake);
}
});
This doesn't work with function registering in the Application on the watchPosition. But this does work with function in the cypress-step file. (Working as in in the console.log I see changes in position according to the values I send in via originalWatchPositionSuccessCallback ).
Any idea who to fake a position change?
There is a different way to solve the issue of getting the callbacks of the registered function to navigator.geolocation.watchPosition triggered. The code in the question tried to solve this by cy.stub(win.navigator.geolocation, "watchPosition"), but this turned to be not working reliably (too soon, too late, different browser/window context, another iframe, ...), the precise reason varied.
An alternative solution to trigger the registered watchPosition callbacks without modifying the production code is the undocumented cypress (v6.2) automation interface in cypress to CDP.
export const setFakePosition = position => {
// https://chromedevtools.github.io/devtools-protocol/tot/Emulation/#method-setGeolocationOverride
console.debug(`cypress::setGeolocationOverride with position ${JSON.stringify(position)}`);
cy.log("**setGeolocationOverride**").then(() =>
Cypress.automation("remote:debugger:protocol", {
command: "Emulation.setGeolocationOverride",
params: {
latitude: position.latitude,
longitude: position.longitude,
accuracy: 50
}
})
);
};
And verifying with:
let positionLogSpy;
When("vehicle is located in {string}", city => {
const position = cityLocationMap[city];
cy.window()
.then(win => {
const expectedLogMessage = `new position lat: ${position.latitude}, lng: ${position.longitude}`;
positionLogSpy = cy.spy(win.console, "log").withArgs(expectedLogMessage);
})
.then(() => {
setFakePosition(position);
});
});
Then("vehicle has moved to {string}", () => {
expect(positionLogSpy).to.be.called;
});

Testing Cross Browser Extension With Jest, How To Mock Chrome Storage API?

After putting off testing for a while now due to Cypress not allowing visiting chrome:// urls, I decided to finally understand how to unit/integration test my extension - TabMerger. This comes after the many times that I had to manually test the ever growing functionality and in some cases forgot to check a thing or two. Having automated testing will certainly speed up the process and help me be more at peace when adding new functionality.
To do this, I chose Jest since my extension was made with React (CRA). I also used React Testing Library (#testing-library/react) to render all React components for testing.
As I recently made TabMerger open source, the full testing script can be found here
Here is the test case that I want to focus on for this question:
import React from "react";
import { render, fireEvent } from "#testing-library/react";
import * as TabFunc from "../src/Tab/Tab_functions";
import Tab from "../src/Tab/Tab";
var init_groups = {
"group-0": {
color: "#d6ffe0",
created: "11/12/2020 # 22:13:24",
tabs: [
{
title:
"Stack Overflow - Where Developers Learn, Share, & Build Careersaaaaaaaaaaaaaaaaaaaaaa",
url: "https://stackoverflow.com/",
},
{
title: "lichess.org • Free Online Chess",
url: "https://lichess.org/",
},
{
title: "Chess.com - Play Chess Online - Free Games",
url: "https://www.chess.com/",
},
],
title: "Chess",
},
"group-1": {
color: "#c7eeff",
created: "11/12/2020 # 22:15:11",
tabs: [
{
title: "Twitch",
url: "https://www.twitch.tv/",
},
{
title: "reddit: the front page of the internet",
url: "https://www.reddit.com/",
},
],
title: "Social",
},
};
describe("removeTab", () => {
it("correctly adjusts groups and counts when a tab is removed", () => {
var tabs = init_groups["group-0"].tabs;
const { container } = render(<Tab init_tabs={tabs} />);
expect(container.getElementsByClassName("draggable").length).toEqual(3);
var removeTabSpy = jest.spyOn(TabFunc, "removeTab");
fireEvent.click(container.querySelector(".close-tab"));
expect(removeTabSpy).toHaveBeenCalledTimes(1);
expect(container.getElementsByClassName("draggable").length).toEqual(2); // fails (does not remove the tab for some reason)
});
});
I mocked the Chrome API according to my needs, but feel that something is missing. To mock the Chrome API I followed this post (along with many others, even for other test runners like Jasmine): testing chrome.storage.local.set with jest.
Even though the Chrome storage API is mocked, I think the issue lies in this function which gets called upon initial render. That is, I think the chrome.storage.local.get is not actually being executed, but am not sure why.
// ./src/Tab/Tab_functions.js
/**
* Sets the initial tabs based on Chrome's local storage upon initial render.
* If Chrome's local storage is empty, this is set to an empty array.
* #param {function} setTabs For re-rendering the group's tabs
* #param {string} id Used to get the correct group tabs
*/
export function setInitTabs(setTabs, id) {
chrome.storage.local.get("groups", (local) => {
var groups = local.groups;
setTabs((groups && groups[id] && groups[id].tabs) || []);
});
}
The reason I think the mocked Chrome storage API is not working properly is because when I manually set it in my tests, the number of tabs does not increase from 0. Which forced me to pass a prop (props.init_tabs) to my Tab component for testing purposes (https://github.com/lbragile/TabMerger/blob/f78a2694786d11e8270454521f92e679d182b577/src/Tab/Tab.js#L33-L35) - something I want to avoid if possible via setting local storage.
Can someone point me in the right direction? I would like to avoid using libraries like jest-chrome since they abstract too much and make it harder for me to understand what is going on in my tests.
I think I have a solution for this now, so I will share with others.
I made proper mocks for my chrome storage API to use localStorage:
// __mocks__/chromeMock.js
...
storage: {
local: {
...,
get: function (key, cb) {
const item = JSON.parse(localStorage.getItem(key));
cb({ [key]: item });
},
...,
set: function (obj, cb) {
const key = Object.keys(obj)[0];
localStorage.setItem(key, JSON.stringify(obj[key]));
cb();
},
},
...
},
...
Also, to simulate the tab settings on initial render, I have a beforeEach hook which sets my localStorage using the above mock:
// __tests__/Tab.spec.js
var init_ls_entry, init_tabs, mockSet;
beforeEach(() => {
chrome.storage.local.set({ groups: init_groups }, () => {});
init_ls_entry = JSON.parse(localStorage.getItem("groups"));
init_tabs = init_ls_entry["group-0"].tabs;
mockSet = jest.fn(); // mock for setState hooks
});
AND most importantly, when I render(<Tab/>), I noticed that I wasn't supplying the id prop which caused nothing to render (in terms of tabs from localStorage), so now I have this:
// __tests__/Tab.spec.js
describe("removeTab", () => {
it("correctly adjusts storage when a tab is removed", async () => {
const { container } = render(
<Tab id="group-0" setTabTotal={mockSet} setGroups={mockSet} />
);
var removeTabSpy = jest.spyOn(TabFunc, "removeTab");
var chromeSetSpy = jest.spyOn(chrome.storage.local, "set");
fireEvent.click(container.querySelector(".close-tab"));
await waitFor(() => {
expect(chromeSetSpy).toHaveBeenCalled();
});
chrome.storage.local.get("groups", (local) => {
expect(init_tabs.length).toEqual(3);
expect(local.groups["group-0"].tabs.length).toEqual(2);
expect(removeTabSpy).toHaveBeenCalledTimes(1);
});
expect.assertions(4);
});
});
Which passes!!
Now on to drag and drop testing 😊

Would I be able to give access to specific electron APIs safely?

So I need to find a way to create custom window titleBar buttons that I can add functionality to safely without enabling nodeIntegration in electron. I was thinking the preload might be what I need but I'm not sure how this works or if it would work for this.
Since I'm creating custom window buttons with HTML, CSS and Javascript, I need these methods:
mainWindow.minimize();
mainWindow.close();
mainWindow.getBounds();
mainWindow.setBounds(...);
mainWindow.setResizable(...);
This is in the renderer process so nodeIntegration would need to be enabled and would need to use remote like this:
const { remote } = require('electron');
const mainWindow = remote.getCurrentWindow();
Would I be able to use the preload option with nodeIntegration disabled to access these methods to add functionality to my custom buttons? If so, how? Would it be safe this way?
You could add a preload script which provides some APIs, just like the following one:
const { remote } = require("electron");
function initialise () {
window.Controls = {
minimize: () => { remote.getCurrentWindow ().minimize (); },
close: () => { remote.getCurrentWindow ().close (); },
getBounds: () => { remote.getCurrentWindow ().getBounds (); },
setBounds: (bounds) => { remote.getCurrentWindow ().setBounds (bounds); },
setResizable: (resizable) => { remote.getCurrentWindow ().setResizable (resizeable); }
};
}
initialise ();
Then, you can use the functions defined like this in your renderer process:
document.getElementById ("close-button").addEventListener ("click", (e) => {
window.Controls.close ();
});
This reduces the risk of executing insecure code by just setting nodeIntegration: true on the BrowserWindow. However, all code which has access to window.Controls will be able to manipulate the window state.

Why does LokiSFSAdapter work on Linux, but not on Windows?

TL;DR A piece of Javascript code works flawlessly on Linux whilst behaving inconsistently on Windows.
I am coding an Electron app, using Vue.js for frontend, Vuex for data management and LokiJS for persistence storage (with its File System adapter at the background). I develop this application on Linux, but from time to time I have to switch to Windows to create a Windows build for the client. The Linux build always works flawlessly, the Windows one misbehaves. I assumed it was a LokiJS issue, however, upon the isolation of LokiJS-specific code, it worked properly even on Windows.
Here is simplified store.js file, which contains all relevant Vuex and LokiJS-related code in my application.
import loki from 'lokijs'
import LokiSFSAdapter from 'lokijs/src/loki-fs-structured-adapter'
import MainState from '../index' // a Vuex.Store object
const state = {
ads: [],
profiles: []
}
var sfsAdapter = new LokiSFSAdapter('loki')
var db = new loki('database.json', {
autoupdate: true,
autoload: true,
autoloadCallback: setupHandler,
adapter: sfsAdapter
})
function setupCollection (collectionName) {
var collection = db.getCollection(collectionName)
if (collection === null) {
collection = db.addCollection(collectionName)
}
}
function setupHandler () {
setupCollection('ads')
setupCollection('profiles')
MainState.commit('updateAds')
MainState.commit('updateProfiles')
}
window.onbeforeunload = function () {
db.saveDatabase()
db.close()
}
const mutations = {
updateAds (state) {
state.ads = db.getCollection('ads').data.slice()
},
updateProfiles (state) {
state.profiles = db.getCollection('profiles').data.slice()
}
}
const actions = {
async addProfile (context) {
db.getCollection('profiles').insert({ /* default data */ })
db.saveDatabase()
context.commit('updateProfiles')
},
async updateProfile (context, obj) {
db.getCollection('profiles').update(obj)
db.saveDatabase()
context.commit('updateProfiles')
},
async deleteProfile (context, id) {
db.getCollection('profiles').removeWhere({'$loki': {'$eq': id}})
db.saveDatabase()
context.commit('updateProfiles')
},
async addAd (context) {
db.getCollection('ads').insert({ /* default data */ })
db.saveDatabase()
context.commit('updateAds')
},
async deleteAd (context, id) {
db.getCollection('ads').removeWhere({'$loki': {'$eq': id}})
db.saveDatabase()
context.commit('updateAds')
}
}
Behaviour on Linux
it calls setupHandler every time the application starts,
it correctly saves data to database.json and the respective collections to database.json.0 and database.json.1 for ads and profiles
when addAd() is called, it can access all the data properly by calling db.getCollection('ads'), and then insert() on it.
Behaviour on Windows
only calls setupHandler if database.json doesn't exist. It correctly creates database.json if it doesn't exist, though.
creates only one file - database.json.0, but doesn't save any data there, it's just an empty file. It doesn't even create database.json.1 for the second collection.
obviously, since no data is actually saved, db.getCollection('ads') and returns null, which results into TypeError: Cannot read property 'insert' of null when calling addAd() on the successive application runs.
if this run database.json was created, the application behaves normally, insert() seems to work, however, no data is saved on exit and the successive runs result in the behaviour in the point above.
Question
Is this a bug somewhere deep in LokiJS/Vuex, or is it just me misusing their API?

Categories

Resources