Prevent Emscripten compiled JavaScript from blocking certain key inputs - javascript

I have emscripten compiled c++ code (using openGL) that runs in my webpage(converted to WebGL) and renders an image. Everything is working great, Except that the Emscripten compiled Javascript blocks my usage of the "delete" "backspace" "tab" keys when I type into a textarea on the webpage. Note that all the letter keys and "space" work just fine.
For reference, the Module I have instantiated in my JavaScript code is almost exactly the same as the default module:
var Module = {
preRun: [],
postRun: [],
print: (function() {
var element = document.getElementById('emscriptenOutput');
if (element) element.value = ''; // clear browser cache
return function(text) {
if (arguments.length > 1) text = Array.prototype.slice.call(arguments).join(' ');
// These replacements are necessary if you render to raw HTML
//text = text.replace(/&/g, "&");
//text = text.replace(/</g, "<");
//text = text.replace(/>/g, ">");
//text = text.replace('\n', '<br>', 'g');
console.log(text);
if (element) {
element.value += text + "\n";
element.scrollTop = element.scrollHeight; // focus on bottom
}
};
})(),
printErr: function(text) {
if (arguments.length > 1) text = Array.prototype.slice.call(arguments).join(' ');
if (0) { // XXX disabled for safety typeof dump == 'function') {
dump(text + '\n'); // fast, straight to the real console
} else {
console.error(text);
}
},
canvas: (function() {
var canvas = document.getElementById('canvas');
// As a default initial behavior, pop up an alert when webgl context is lost. To make your
// application robust, you may want to override this behavior before shipping!
// See http://www.khronos.org/registry/webgl/specs/latest/1.0/#5.15.2
canvas.addEventListener("webglcontextlost", function(e) { alert('WebGL context lost. You will need to reload the page.'); e.preventDefault(); }, false);
return canvas;
})(),
totalDependencies: 0,
//doNotCaptureKeyboard: true,
monitorRunDependencies: function(left) {
this.totalDependencies = Math.max(this.totalDependencies, left);
// Module.setStatus(left ? 'Preparing... (' + (this.totalDependencies-left) + '/' + this.totalDependencies + ')' : 'All downloads complete.');
}
};
(function() {
var memoryInitializer = 'renderer.js.mem';
if (typeof Module['locateFile'] === 'function') {
memoryInitializer = Module['locateFile'](memoryInitializer);
} else if (Module['memoryInitializerPrefixURL']) {
memoryInitializer = Module['memoryInitializerPrefixURL'] + memoryInitializer;
}
var meminitXHR = Module['memoryInitializerRequest'] = new XMLHttpRequest();
meminitXHR.open('GET', memoryInitializer, true);
meminitXHR.responseType = 'arraybuffer';
meminitXHR.send(null);
})();
var script = document.createElement('script');
script.src = "renderer.js";
document.body.appendChild(script);
Ofcourse, the emscripten compiled JavaScript is gibberish to the human eye but the culprit most likely lies in there.
My guess is that the WebGL context is eating the keyboard events, but I'm not sure. I've looked at these links and tried to include "doNotCaptureKeyboard: true" to the module element, but with no success.
https://forum.unity.com/threads/disable-enable-keyboard-in-runtime-webgl.286557/#post-1892527
https://github.com/kripken/emscripten/issues/2668#event-154218404
Anyone have any experience with the same sort of issue? I'm at a loss.

For those curious, I've got a solution but not sure if it's the best. I've just added event listeners of my own to block the event listeners of the compiled JavaScript code:
/* code to prevent emscripten compiled code from eating key input */
window.addEventListener('keydown', function(event){
event.stopImmediatePropagation();
}, true);
window.addEventListener('keyup', function(event){
event.stopImmediatePropagation();
}, true);

Inside emscripten src/library_glfw.js (around line 400, milage might vary on your version) there's a very bizarre check that goes like this:
// This logic comes directly from the sdl implementation. We cannot
// call preventDefault on all keydown events otherwise onKeyPress will
// not get called
if (event.keyCode === 8 /* backspace */ || event.keyCode === 9 /* tab */) {
event.preventDefault();
}
Now If you comment out those lines those events will get propagated correctly, I'm not sure why they've put that SDL special case inside a generic code path.
PS: This answer will probably be outdated if they ever fix that bit of
code, so if you are reading it from the future just use it as a past
reference.

Related

Add a custom key shortcut within Javascript in Confluence

On my work we use Confluence. I need shortcut for monospace formatting on an edit page.
Native shortcut is CTRL+SHIFT+M. It's taken in Opera by MyFlow feature and cannot be changed.
Is there an option to make it by using Javascript code? (I can make JS injection in a browser extension.)
Regular JS code for shortcuts which works fine but not on a confluence edit page:
// define a handler
function monospaceKeyTrigger(e) {
// this would test for whichever key is 40 and the ctrl key at the same time
if (e.ctrlKey && e.shiftKey && e.keyCode == 90) {
// trigger click on monospace button
//document.getElementById("rte-monospace").click();
alert('!!monospace!!');
}
}
// register the handler
document.addEventListener('keyup', monospaceKeyTrigger, false);
So, what I've missed?
I guess, it's not triggering at all due to editor JS functionality...
Any advice guys?
Found.
//Set CTRL+SHIFT+L shortcut for monospace formatting in the editor
window.AJS.Rte.getEditor().shortcuts.add("ctrl+shift+l","monospace","confMonospace");
Cheers
P.S.
Thanks for this posts:
https://webapps.stackexchange.com/questions/35383/shortcut-key-for-monospaced-character-format-in-confluence (outdated, but helped to understand how to pass arguments)
https://searchcode.com/codesearch/view/37330905/ #47, take a look shortcuts list in Confluence.KeyboardShortcuts
P.P.S. Browser-ready Javascript code (tested in Atlassian Confluence 6.15.2)
Simple 👌:
// Set monospace formatting for a key shortcut in confluence
// Use a browser extension for injecting this code snippet
(function () {
window.AJS.Rte.getEditor().shortcuts.add(
'ctrl+shift+l',
"monospace",
"confMonospace"
);
}());
Overprotected 😀 :
// Set monospace formatting for a key shortcut in confluence
// Use a browser extension for injecting this code snippet
console.log('include CJS');
let confKeyAdd = {
run: function () {
this.key = {
keyCode: 'ctrl+shift+l',
codeType: 'monospace',
codeConfType: 'confMonospace'
};
this.setKey();
},
waiter: function (shouldWaitCall, successCall, repeat = 10, interval = 1000) {
let timerId;
//wait here
timerId = setInterval(
function () {
if (--repeat < 0) {
console.log('confKeyAdd: Have not found an object.');
clearTimeout(timerId);
return;
}
if (shouldWaitCall()) {
console.log('confKeyAdd: Still waiting... [' + repeat + ']');
return;
}
clearTimeout(timerId);
// call me!
successCall();
},
interval
);
},
setKey() {
let _this = this;
// first call: should-wait
// second call: success
this.waiter(
function () {
console.log('confKeyAdd: Checking...');
return typeof window.AJS === 'undefined'
|| window.AJS.Rte.getEditor() === null
|| !window.AJS.Rte.getEditor().shortcuts;
},
function () {
console.log('confKeyAdd: Adding a key shortcut for: ' + _this.key.keyCode);
window.AJS.Rte.getEditor().shortcuts.add(
_this.key.keyCode,
_this.key.codeType,
_this.key.codeConfType
);
},
);
}
};
confKeyAdd.run();

infinite scroll on squarespace get category filter

I am using this code to infinite load a page on squarespace. My problem is the reloading doesn't capture the filtering that I have set up in my url. It cannot seem to 'see' the variables or even the url or categoryFilter in my collection. I've tried to use a .var directive but the lazy loaded items cannot see the scope of things defined before it. I'm running out of ideas here please help!
edit: I've since found the answer but gained another question.
I was able to use window.location.href instead of window.location.pathname to eventually get the parameters that way. Except this doesn't work in IE11 so now I have to search for this.
<script>
function infiniteScroll(parent, post) {
// Set some variables. We'll use all these later.
var postIndex = 1,
execute = true,
stuffBottom = Y.one(parent).get('clientHeight') + Y.one(parent).getY(),
urlQuery = window.location.pathname,
postNumber = Static.SQUARESPACE_CONTEXT.collection.itemCount,
presentNumber = Y.all(post).size();
Y.on('scroll', function() {
if (presentNumber >= postNumber && execute === true) {
Y.one(parent).append('<h1>There are no more posts.</h1>')
execute = false;
} else {
// A few more variables.
var spaceHeight = document.documentElement.clientHeight + window.scrollY,
next = false;
/*
This if statement measures if the distance from
the top of the page to the bottom of the content
is less than the scrollY position. If it is,
it's sets next to true.
*/
if (stuffBottom < spaceHeight && execute === true) {
next = true;
}
if (next === true) {
/*
Immediately set execute back to false.
This prevents the scroll listener from
firing too often.
*/
execute = false;
// Increment the post index.
postIndex++;
// Make the Ajax request.
Y.io(urlQuery + '?page=' + postIndex, {
on: {
success: function (x, o) {
try {
d = Y.DOM.create(o.responseText);
} catch (e) {
console.log("JSON Parse failed!");
return;
}
// Append the contents of the next page to this page.
Y.one(parent).append(Y.Selector.query(parent, d, true).innerHTML);
// Reset some variables.
stuffBottom = Y.one(parent).get('clientHeight') + Y.one(parent).getY();
presentNumber = Y.all(post).size();
execute = true;
}
}
});
}
}
});
}
// Call the function on domready.
Y.use('node', function() {
Y.on('domready', function() {
infiniteScroll('#content','.lazy-post');
});
});
</script>
I was able to get this script working the way I wanted.
I thought I could use:
Static.SQUARESPACE_CONTEXT.collection.itemCount
to get {collection.categoryFilter} like with jsont, like this:
Static.SQUARESPACE_CONTEXT.collection.categoryFilter
or this:
Static.SQUARESPACE_CONTEXT.categoryFilter
It didn't work so I instead changed
urlQuery = window.location.pathname
to
urlQuery = window.location.href
which gave me the parameters I needed.
The IE11 problem I encountered was this script uses
window.scrollY
I changed it to the ie11 compatible
Window.pageYOffset
and we were good to go!

Firefox, javascript and iFrame performance with Jquery

I'm having a bit of a jquery javascript performance issue, specifically related to Firefox.
We have a set of vimeo embeds, and the ids are pulled in via a json file. On each click, a new video is displayed. After the video is played, the container is removed and the title cloud is put back in. After a certain number of rounds, Firefox performance seriously degrades and you get the "unresponsive script" error. This isn't happening on any other browsers. Furthermore, the profiler in FF doesn't seem to point to a root cause of the slowdown.
I believe this is caused by poor iframe performance and how FF handles iframes, but I'm not entirely sure about this. Nothing else I'm doing is anything too, mostly just stock jquery functions like empty(), remove(), prepend(), etc.
I have implemented a click counter which will just refresh the page after a certain amount of click throughs. This resolved the problem, but it's a hacky solution which I seriously dislike. I would love some ideas on the root cause of this and any advice on how to solve it.
Here's the link to the site and the specific portion mentioned:
http://www.wongdoody.com/mangles
This isn't all the code, but this is the part that gets called every click.
Also, I have tried just swapping out the src="" in the iframe, but performance still degrades.
EDIT: I can confirm this is not a memory leak, I used about:memory and with addons disabled in safe mode I'm getting decent memory usage:
359.11 MB ── private
361.25 MB ── resident
725.54 MB ── vsize
Something in the vimeo embed is slowing down the javascript engine, but it's not a memory leak. Also, this is confirmed by the fact that I can resolve the issue by just refreshing the page. If it was a memory leak I would have to close FF altogether.
function getIframeContent(vid) {
mangle_vid_id = vid;
return '<div class="vimeoContainerflex"><div class="vimeoContainer"><iframe class="vimeo" style="z-index:1;" width="100%" height="100%" frameborder="0" allowfullscreen="" mozallowfullscreen="" webkitallowfullscreen="" src="//player.vimeo.com/video/' + mangle_vid_id + '?api=1&title=0&color=89ff18&byline=0&portrait=0&autoplay=1"></iframe></div></div>';
}
function show_titles() {
$('.mangle-btn').hide();
$('.vimeoContainerflex').remove();
$('span.mangle').hide();
if ($('#mangle-titles').length < 1) {
$('#wongdoody').prepend(wd_titles_content);
}
$('#arrow').show();
if (clicks > 12) {
location.reload();
}
$('#mangle-titles span').click(function() {
clicks = clicks + 1;
$('#mangle-wrapper').remove();
var vidID = $(this).attr('data-id');
if ($('.vimeoContainer').length < 1) {
if (vidID == "home") {
$('#wongdoody').prepend(getIframeContent(getRandom()));
} else {
$('#wongdoody').prepend(getIframeContent(vidID));
}
}
$('#arrow').hide();
vimeoAPI();
});
$('#mangle-titles span').not('noscale').each(function() {
var _this = $(this);
var classname = _this.attr('class');
var scaleNum = classname.substr(classname.length - 2);
var upscale = parseInt(scaleNum);
var addition = upscale + 5;
var string = addition.toString();
_this.hover(
function() {
_this.addClass('scale' + string);
},
function() {
_this.removeClass('scale' + string);
}
);
});
}
function vimeoAPI() {
var player = $('iframe');
var url = window.location.protocol + player.attr('src').split('?')[0];
var status = $('.status');
// Listen for messages from the player
if (window.addEventListener) {
window.addEventListener('message', onMessageReceived, false);
} else {
window.attachEvent('onmessage', onMessageReceived, false);
}
// Handle messages received from the player
function onMessageReceived(e) {
var data = JSON.parse(e.data);
switch (data.event) {
case 'ready':
onReady();
break;
case 'finish':
onFinish();
break;
}
}
// Helper function for sending a message to the player
function post(action, value) {
var data = {
method: action
};
if (value) {
data.value = value;
}
var message = JSON.stringify(data);
if (player[0].contentWindow != null) player[0].contentWindow.postMessage(data, url);
}
function onReady() {
post('addEventListener', 'finish');
}
function onFinish() {
setTimeout(show_titles, 500);
}
}
Part of you're problem may be the fact that you keep adding more and more click-handlers to the spans. After each movie ends the onFinish function calls show_titles again, which attaches a new (=additional) click-handler to the $('#mangle-titles span') spans. jQuery does not remove previously attached handlers.
Try splitting the show_titles function into two. init_titles should be called only once:
function init_titles() {
if ($('#mangle-titles').length < 1) {
$('#wongdoody').prepend(wd_titles_content);
}
$('#mangle-titles span').click(function() {
$('#mangle-wrapper').remove();
var vidID = $(this).attr('data-id');
if ($('.vimeoContainer').length < 1) {
if (vidID == "home") {
$('#wongdoody').prepend(getIframeContent(getRandom()));
} else {
$('#wongdoody').prepend(getIframeContent(vidID));
}
}
$('#arrow').hide();
vimeoAPI();
});
$('#mangle-titles span').not('noscale').each(function() {
var _this = $(this);
var classname = _this.attr('class');
var scaleNum = classname.substr(classname.length - 2);
var upscale = parseInt(scaleNum);
var addition = upscale + 5;
var string = addition.toString();
_this.hover(
function() {
_this.addClass('scale' + string);
},
function() {
_this.removeClass('scale' + string);
}
);
});
}
function show_titles() {
$('.mangle-btn').hide();
$('.vimeoContainerflex').remove();
$('span.mangle').hide();
$('#arrow').show();
}
I'd recommend trying to re-use the iframe instead of wiping and re-adding. Failing that, I think you may be out of luck. Your method of closing the iFrame is fine; your browser that it's running in is not.
You're overloading window with eventListeners. Each time a user clicks a video, you're attaching an event to window that fires every time you're receiving a message.
You can easily check this by adding console.log("Fire!"), for instance, at the beginning of onMessageReceived. You'll see that this function gets triggered an awful number of times after the user has performed some clicks on videos.
That surely has an impact on performance.
Hope this helps.

Prevent other scripts from setting window.title?

I'm working on a chrome extension, and I set window.title in the onload handler. It seems, though, that the page I'm modifying sets the document title dynamically as well. There's a huge collection of scripts being linked. Is there any way for me to prevent anyone else from modifying document.title or any of its variants, without knowing where the modification is coming from? Alternatively, is there a quick way for me to see where the change is coming from?
I had same problem, some external scripts are changed my page title by document.title = "..."
I've made own solution for it:
try {
window.originalTitle = document.title; // save for future
Object.defineProperty(document, 'title', {
get: function() {return originalTitle},
set: function() {}
});
} catch (e) {}
See the answer to how to listen for changes to the title element?. Notably:
function titleModified() {
window.alert("Title modifed");
}
window.onload = function() {
var titleEl = document.getElementsByTagName("title")[0];
var docEl = document.documentElement;
if (docEl && docEl.addEventListener) {
docEl.addEventListener("DOMSubtreeModified", function(evt) {
var t = evt.target;
if (t === titleEl || (t.parentNode && t.parentNode === titleEl)) {
titleModified();
}
}, false);
} else {
document.onpropertychange = function() {
if (window.event.propertyName == "title") {
titleModified();
}
};
}
};
This SO answer suggest a technique for how to listen for changes to the document title.
Perhaps you could use that technique to create a callback which changes the title back to whatever you want it to be, as soon as some other script tries to change it.

Detecting when an iframe gets or loses focus

What's the correct way of detecting when an iframe gets or loses focus (i.e. will or will not receive keyboard events)? The following is not working in Fx4:
var iframe = /* my iframe */;
iframe.addEventListener("focus", function() { /* never gets called */ }, false);
You can poll "document.activeElement" to determine if it matches the iframe. Polling isn't ideal, but it works:
function checkFocus() {
if(document.activeElement == document.getElementsByTagName("iframe")[0]) {
console.log('iframe has focus');
} else {
console.log('iframe not focused');
}
}
window.setInterval(checkFocus, 1000);
i know it's old, but i also had the same problem.
i ended up using this little pice of code:
$(document).on('focusout', function(){
setTimeout(function(){
// using the 'setTimout' to let the event pass the run loop
if (document.activeElement instanceof HTMLIFrameElement) {
// Do your logic here..
}
},0);
});
Turns out it's not really possible. I had to change the logic of my page to avoid the need of tracking if the iframe has focus.
How to check when an iframe has been clicked in or out of as well as hover-state.
Note: I would highly recommend you don't choose a polling method and go with an event driven method such as this.
Disclaimer
It is not possible to use the focus or blur events directly on an iframe but you can use them on the window to provide an event driven method of checking the document.activeElement. Thus you can accomplish what you're after.
Although we're now in 2018, my code is being implemented in GTM and tries to be cross browser compatible back to IE 11. This means there's more efficient code if you're utilizing newer ES/ECMAScript features.
Setup
I'm going to take this a few steps further to show that we can also get the iframe's src attribute as well as determine if it's being hovered.
Code
You would ideally need to put this in a document ready event, or at least encapsulate it so that the variables aren't global [maybe use an IIFE]. I did not wrap it in a document ready because it's handled by GTM. It may also depend where you place this or how you're loading it such as in the footer.
https://jsfiddle.net/9285tbsm/9/
I have noticed in the JSFiddle preview that it's already an iframe, sometimes you have to focus it first before events start to capture. Other issues can be that your browser window isn't yet focused either.
// Helpers
var iframeClickedLast;
function eventFromIframe(event) {
var el = event.target;
return el && el.tagName && el.tagName.toLowerCase() == 'iframe';
}
function getIframeSrc(event) {
var el = event.target;
return eventFromIframe(event) ? el.getAttribute('src') : '';
}
// Events
function windowBlurred(e) {
var el = document.activeElement;
if (el.tagName.toLowerCase() == 'iframe') {
console.log('Blurred: iframe CLICKED ON', 'SRC:', el.getAttribute('src'), e);
iframeClickedLast = true;
}
else {
console.log('Blurred', e);
}
}
function windowFocussed(e) {
if (iframeClickedLast) {
var el = document.activeElement;
iframeClickedLast = false;
console.log('Focussed: iframe CLICKED OFF', 'SRC:', el.getAttribute('src'), e);
}
else {
console.log('Focussed', e);
}
}
function iframeMouseOver(e) {
console.log('Mouse Over', 'SRC:', getIframeSrc(e), e);
}
function iframeMouseOut(e) {
console.log('Mouse Out', 'SRC:', getIframeSrc(e), e);
}
// Attach Events
window.addEventListener('focus', windowFocussed, true);
window.addEventListener('blur', windowBlurred, true);
var iframes = document.getElementsByTagName("iframe");
for (var i = 0; i < iframes.length; i++) {
iframes[i].addEventListener('mouseover', iframeMouseOver, true);
iframes[i].addEventListener('mouseout', iframeMouseOut, true);
}
I have solved this by using contentWindow instead of contentDocument.
The good thing about contentWindow is
it works also in case user clicks another window (another application) or another browser tab. If using activeElement, if user clicks away from the entire window to go to another application, then that logic still think the iframe is in focus, while it is not
and we don't need to poll and do a setInterval at all. This uses the normal addEventListener
let iframe = document.getElementsByTagName("iframe")[0];
// or whatever way you do to grab that iFrame, say you have an `id`, then it's even more precise
if(iframe){
iframeWindow = iframe.contentWindow;
iframeWindow.addEventListener('focus', handleIframeFocused);
iframeWindow.addEventListener('blur', handleIframeBlurred);
}
function handleIframeFocused(){
console.log('iframe focused');
// Additional logic that you need to implement here when focused
}
function handleIframeBlurred(){
console.log('iframe blurred');
// Additional logic that you need to implement here when blurred
}
This solution is working for me on both mobile and desktop:
;(function pollForIframe() {
var myIframe = document.querySelector('#my_iframe');
if (!myIframe) return setTimeout(pollForIframe, 50);
window.addEventListener('blur', function () {
if (document.activeElement == myIframe) {
console.log('myIframe clicked!');
}
});
})();
The solution is to inject a javascript event on the parent page like this :
var script = document.createElement('script');
script.type = 'text/javascript';
script.innerHTML =
"document.addEventListener('click', function()" +
"{ if(document.getElementById('iframe')) {" +
// What you want
"}});";
head.appendChild(script);
Here is the code to Detecting when an iframe gets or loses focus
// This code can be used to verify Iframe gets focus/loses.
function CheckFocus(){
if (document.activeElement.id == $(':focus').context.activeElement.id) {
// here do something
}
else{
//do something
}
}
A compact function that accepts callbacks you want to run when iframe gets or loses focus.
/* eslint-disable no-unused-vars */
export default function watchIframeFocus(onFocus, onBlur) {
let iframeClickedLast;
function windowBlurred(e) {
const el = document.activeElement;
if (el.tagName.toLowerCase() == 'iframe') {
iframeClickedLast = true;
onFocus();
}
}
function windowFocussed(e) {
if (iframeClickedLast) {
iframeClickedLast = false;
onBlur();
}
}
window.addEventListener('focus', windowFocussed, true);
window.addEventListener('blur', windowBlurred, true);
}
This might work
document.addEventListener('click', function(event) {
var frame= document.getElementById("yourFrameID");
var isClickInsideFrame = frame.contains(event.target);
if (!isClickInsideFrame ) {
//exec code
}
});

Categories

Resources