My intention is for one message to be passed to the worker after it is created, and for that message to be logged by the worker and a reply to be sent back. What happens is that the message sent to the web worker is logged twice, and only one reply is sent. If you refresh the page or pause execution using developer tools, the message is correctly logged once.
I created a miniature extension to demonstrate this. There is a background script listening for a click on the browser action button. When it is clicked, a new tab is opened, and this tab generates 2 web workers. These workers listen for messages and log them, and send a reply stating that the message was recieved. The newly opened tab then sends the message, and logs the reply.
Google drive with all of the files needed to run this as a chrome extension
Image demonstrating the double logging of the message
Some references:
general web worker
use
.postMessage to
worker
chrome browser action
chrome tabs API
I have been working on my extension for a while now and have run into all sorts of fun bugs, particularly with async code. In the end I was always able to either debug them myself or find a reference that explained the problem. Thank you for any help you can give!
background.js
const tabUrl = chrome.extension.getURL("tab.html");
function browserActionCallback(tab) {
function createResultsTab() {
chrome.tabs.create({
"url": tabUrl,
"index": tab.index + 1
}, function() {
console.log("Tab created");
});
}
(function tabHandler() {
chrome.tabs.query({"url":tabUrl}, (tabQueryResults) => {
if (tabQueryResults.length > 0) {
chrome.tabs.remove(tabQueryResults[0].id, () => {
createResultsTab();
});
} else {
createResultsTab();
}
});
})();
}
chrome.browserAction.onClicked.addListener((tab) => {
browserActionCallback(tab);
});
tab.js
function createWorkers(num) {
/*in original extension I intended to let the user change number of workers in options*/
var workers = new Array(num);
for (let i = 0; i < num; i++) {
workers[i] = new Worker("worker.js");
}
return workers;
}
function messageWorkers(workers) {
let len = workers.length
for (let i = 0; i < len; i++) {
let message = "Connection " + i + " started";
workers[i].postMessage(message);
workers[i].onmessage = function(e) {
console.log(e.data);
}
}
}
var numWorkers = 2;
const WORKERS = createWorkers(numWorkers);
console.log("Before");
messageWorkers(WORKERS);
console.log("After");
worker.js
onmessage = function(msg) {
console.log(msg.data);
postMessage("Worker reply- " + msg.data);
}
EDIT 1: changing the tab.js messageWorkers function's for loop such that onmessage is set before postMessage does not change the results. Sending multiple messages also doesn't change results. If I have a console.log statement at the start of worker's onmessage function which logs that the function has begun, it too logs twice. To reiterate, this only happens when this tab is created by the background script and not when the page is refreshed or debugger is used.
EDIT 2: in the devtools console, there is a drop down menu to choose between top and workers. Checking the option 'selected context only' makes the repeated logging disappear, however this view is a little narrower than I would like
I can confirm this issue. Sample code to reproduce it:
index.js
function worker() {
console.log('this logged twice');
}
const workerBlob = new Blob(
[worker.toString().match(/function[^{]+\{([\s\S]*)\}$/)[1]],
{type: 'text/javascript'}
);
const workerBlobUrl = URL.createObjectURL(workerBlob);
new Worker(workerBlobUrl);
Use index.js as a Chrome extension background script and "this logged twice" will be logged twice when loading the extension or re-launching Chrome.
Related
I have service worker which handles push notification click event:
self.addEventListener('notificationclick', function (e) {
e.notification.close();
e.waitUntil(
clients.openWindow(e.notification.data.url)
);
});
When notification comes it takes url from data and displays it in new window.
The code works, however, I want different behavior. When User clicks on the link, then it should check if there is any opened window within service worker scope. If yes, then it should focus on the window and navigate to the given url.
I have checked this answer but it is not exactly what I want.
Any idea how it can be done?
P.S. I wrote this code but it still doesn't work. The first two messages are however shown in the log.
self.addEventListener('notificationclick', function (e) {
e.notification.close();
var redirectUrl = e.notification.data.redirect_url.toString();
var scopeUrl = e.notification.data.scope_url.toString();
console.log(redirectUrl);
console.log(scopeUrl);
e.waitUntil(
clients.matchAll({type: 'window'}).then(function(clients) {
for (i = 0; i < clients.length; i++) {
console.log(clients[i].url);
if (clients[i].url.toString().indexOf(scopeUrl) !== -1) {
// Scope url is the part of main url
clients[i].navigate(givenUrl);
clients[i].focus();
break;
}
}
})
);
});
Ok, here is the piece of code which works as expected. Notice that I am passing scope_url together with redirect_url into the web notification. After that I am checking if scope_url is part of sw location. Only after that I navigate to redirect_url.
self.addEventListener('notificationclick', function (e) {
e.notification.close();
var redirectUrl = e.notification.data.redirect_url;
var scopeUrl = e.notification.data.scope_url;
e.waitUntil(
clients.matchAll({includeUncontrolled: true, type: 'window'}).then(function(clients) {
for (i = 0; i < clients.length; i++) {
if (clients[i].url.indexOf(scopeUrl) !== -1) {
// Scope url is the part of main url
clients[i].navigate(redirectUrl);
clients[i].focus();
break;
}
}
})
);
});
If I understand you correctly, most of the code you linked to works here.
First retrieve all the clients
If there are more than one, choose one of them
Navigate that to somewhere and focus
Else open a new window
Right?
event.waitUntil(
clients.matchAll({type: 'window'})
.then(clients => {
// clients is an array with all the clients
if (clients.length > 0) {
// if you have multiple clients, decide
// choose one of the clients here
const someClient = clients[..someindex..]
return someClient.navigate(navigationUrl)
.then(client => client.focus());
} else {
// if you don't have any clients
return clients.openWindow(navigationUrl);
}
})
);
I've created a cordova Android app where users tap a link and it starts jsartoolkit. It all works fine the first time, but if I tap the back button then open the link again, after a few times it fails with an error:
"typeerror: artoolkit.setup is not a function"
It's happening when it tries to load the ARCameraParam (Data/camera_para.dat) - I've tried loading it both directly into the arcontroller ie var arController = new ARController(video, 'Data/camera_para.dat') and also loading it first ie arCamera = new ARCameraParam.
This error suggests that artoolkit.min.js isn't loading after a few times but I'm bamboozled as to why that would be.
It's not a caching issue - if I clear the app cache when it happens then try again, it still won't load. It always works fine the first time I've installed the app and also if I force stop the app, which suggests there's a process running from a previous session I'm not aware of. I've tried calling dispose on both the arcontroller and the arcameraparam when I tap the back button but that hasn't fixed it.
Does anyone know what processes jsartoolkit might be running that might cause an error like this? Any advice is very gratefully received.
Relevant code:
window.ARThreeOnLoad = function() {
function gotStream(stream) {
camvideo.src = window.URL.createObjectURL(stream);
nextBit(camvideo);
track = stream.getVideoTracks()[0];
camvideo.onerror = function(e)
{ stream.stop(); };
stream.onended = noStream;
}
function noStream(e) {
alert("no stream " + e);
var msg = 'No camera available.';
if (e.code == 1) {
msg = 'User denied access to use camera.'; }
document.getElementById('errorMessage').textContent = msg;
}
var camvideo = document.getElementById('monitor');
var constraints = { video: {facingMode: {exact: "environment"}, width: 1280, height: 960} };
navigator.mediaDevices.getUserMedia(constraints).then(gotStream).catch(noStream);
}
function nextBit(video) {
var arController = new ARController(video, 'Data/camera_para.dat');
arController.onload = function() {
arController.image = video;
I want to open a standalone web app added to home screen from a received push notification. Almost everything is working fine, except that when I click the notification when the stand-alone app or a browser is not open, it gives me a random list of other irrelevant apps [screenshot attached].
>
Here is my code on Service Worker where I try to open the app.
self.addEventListener('notificationclick', function(event){
var data = event.notification.data;
event.notification.close();
event.waitUntil(clients.matchAll({
type: 'window'
})
.then(function(clientList) {
for (var i = 0; i < clientList.length; i++) {
var client = clientList[i];
if (client.url.split('#')[0] == 'https://account.info.lk/' && 'focus' in client){
client.navigate(data.url);
return client.focus();
}
}
if (clients.openWindow)
return clients.openWindow(data.url);
}));
});
Please help me get the standalone app (or at least on a browser window open when a push notification is clicked.
I am experiencing a really bizarre bug.
In my background.js file I have a function that closes tabs like so
closeTabs = function(tabIds,category){
for(var i=0; i<tabIds.length;i++){
var url = findTabById(tabIds[i]).url;
console.log(url);
reservedUrls.push(url);
}
console.log(arrToString(reservedUrls));
if(tabIds.length>0){
chrome.tabs.remove(tabIds,function(){
});
}
}
This function is called from my browser action.
I then have an event listener for the remove tab event.
chrome.tabs.onRemoved.addListener(function(tabId){
console.log('removed');
var removedUrl = findTabById(tabId).url;
console.log(removedUrl);
console.log(arrToString(reservedUrls));
var index = reservedUrls.indexOf(removedUrl);
if(index>-1){
console.log('in here');
reservedUrls.splice(index,1);
} else {
var category = findCategoryById(tabId);
if(category){
for(var i=0;i<currentTabs[category];i++){
if(currentTabs[category][i].id==tabId){
currentTabs[category].splice(i,1);
}
}
} else {
for(var i=0;i<ungrouped.length;i++){
if(ungrouped[i].id==tabId){
ungrouped.splice(i,1);
}
}
}
console.log('sending message');
chrome.runtime.sendMessage({type:'removed',tabId:tabId});
}
console.log(currentTabs);
});
When tabs are closed by the first function, I place them in reservedUrls, so that the logic acts differently. When I look at the console of the background page, it enters the if statement like it should. However, in the console of the popup, it logs that it navigated into the else statement.
Has anyone every experience this disparity. If so, what are the typical causes?
You remove tab script is executed in own context, it does not mean it removes tabs synchronously. So your listener gets the event once tab is really removed from the system. You could check this with a page that prevents tab from closing form unload event
I've got a little app that generates a code and stores it in mongodb(My chrome browser). Another user(My firefox browser) enters the given code and broadcasts it to let my chrome know that he's there.
Now i want my chrome browser to emit an agreement to itself and my firefox browser so they both get parsed by the same function the moment the agreement is emitted.
The point however is that i only get 1 console log in my terminal which leads me to think that only Chrome(or Firefox, which i doubt) is listening to the emit.
Can anyone take a look why not both browsers receive the 'agreement' emit?
My app.js: (The on connection part)
io.sockets.on('connection', function (socket) {
socket.on('code_game', function (data) {
if (codeToUse == data.code) {
//The receiving end received the proper code from the sending end
console.log(data.secondUser + ' is verbonden via de code: ' + data.code);
//Emit to all parties to let everyone know there's a connection
socket.emit('agreement', {
userOne: {
name: 'Timen',
code: codeToUse
},
userTwo: {
name: data.secondUser,
code: data.code
}
});
}
});
});
And the JS file being called in my view: (sendToFirstUser is Firefox in this case)
var receivingUsersCode = false;
var receivingUsersName = false;
var socket = io.connect('http://localhost');
socket.on('agreement', function (data) {
console.log("hooray");
});
function setReceivingData(code, username) {
receivingUsersCode = code;
receivingUsersName = username;
ding = 'drie';
$('#new_game').css('display', 'block');
$('.theCode').html(receivingUsersCode);
}
function sendToFirstUser(code, username) {
socket.emit('code_game', { code: code, secondUser: username});
}
I'm not sure I understand exactly what you're asking. But it seems to me like you're asking why both your Chrome and Firefox browser aren't emitting an 'agreement' event. If that's it, I think you've answered your own question:
"Another user(My firefox browser) enters the given code and broadcasts it to let my chrome know that he's there."
//Starts broadcasting to other clients
io.sockets.on('connection', function (socket) {
socket.broadcast.emit('code_game', { code: req.body.code, secondUser: req.body.secondUser});
});
Your firefox browser only emits to other clients (your chrome browser) through socket.broadcast.emit. So, only the chrome browser receives the 'code_game' event on the browser side. But in your browser side code, the client emits the 'agreement' event when it receives the 'code_game' event:
socket.on('code_game', function (data) {
if (receivingUsersCode == data.code) {
console.log(data.secondUser + ' is is connected via code: ' + data.code);
listenForMutualConnection();
socket.emit('agreement', {
userOne: {
name: receivingUsersName,
code: receivingUsersCode
},
userTwo: {
name: data.secondUser,
code: data.code
}
});
}
});
Since only the chrome browser is receiving the 'code_game' event, it's also the only one emitting the 'agreement' event.