Efficient scrolling of piped output in a browser window - javascript

I have a custom browser plugin (built with FireBreath) that will invoke a local process on a users machine and pipe stdout back to the browser, to do this i'm running the process through a popen() call and as I read data from the pipe I fire a JSAPI event and send it back to the browser.
In the browser I append the output to a div as pre-formatted text and tell the div to scroll to the bottom.
Code in the browser plugin:
FILE* in;
if(!(in = _popen(command_string, "r")))
{
return NULL;
}
while(fgets(buff, sizeof(buff), in)!=NULL)
{
send_output_to_browser(buff);
}
HTML & Javascript/jQuery:
<pre id="sync_status_window" style="overflow:scroll">
<span id="sync_output"></span>
</pre>
var onPluginTextReceived = function (text)
{
$('#sync_output').append(text);
var objDiv = document.getElementById('sync_status_window');
objDiv.scrollTop = objDiv.scrollHeight;
}
This method works for the browsers I need it to (this is a limited use internal tool), but it's frustratingly laggy. My process usually finishes about 30-60 seconds before the output window finishes scrolling. So, how do I make this more efficient? Is there a better way to pipe this text back to the browser?

There are two optimizations I see potential in:
keep a reference to your pre and span, you keep repeating the dom
tree search , which is quite costly
Chunk up the output - either on the C side (preferable) or on the JS
side.
For quick hack (without removing dependency on jquery, which should be done) could look like
//Higher or global scope
var pluginBuffer=[];
var pluginTimeout=false;
var sync_status_window=document.getElementById('sync_status_window');
function onPluginTextReceived(text)
{
pluginBuffer[pluginBuffer.length]=text;
if (!pluginTimeout) pluginTimeout=window.SetTimeout('onPluginTimer();',333);
}
function onPluginTimer()
{
var txt=pluginBuffer.join('');
pluginBuffer=[];
pluginTimeout=false;
$('#sync_output').append(text);
sync_status_window.scrollTop = sync_status_window.scrollHeight;
}
Adapt to your needs, I chose 333ms for 3 updates/second

Related

Bookmarklet to change the arbitrary value of a JSON config

I'm working on changing the UX of a proprietary, heavily obfuscated and tamper-proof web application, so I can't nip this in the bud at server level; I'm limited to the sandbox of my browser and bookmarklets.
A page loads up main code (<script preload>) and user config (<script defer>) in two separate queries. I would like for a setting in the user config to have a different value than what the server sends.
Doing it for myself, I could tamper with the config before it even arrives in the browser, but I have to make this work reliably for other users as well, without having them to install a whole bunch of stuff like Greasemonkey, Fiddler, whatever.
I've tried two equally inefficient approaches:
When the page starts loading, execute this and hope I win the race:
{
var loadedConf = document.getElementById("user").innerHTML;
if(typeof loadedConf === "undefined")
{
setTimeout(raceReplace, 5);
rantime += 5;
}
else
{
document.getElementById("user").innerHTML = loadedConf.replace(/arbitraryConfigField":false/,
'arbitraryConfigField":"arbitraryConfigValue"');
console.log("Raced to a finish in " + rantime + "ms")
}
}
raceReplace()
Because it's a race, it's wholly contingent upon how fast a user clicks the bookmarklet. With practice, I can do it almost 50% of the time! Heh.
Extend JSON.parse to make it return an object as it normally would, the way God and Douglas Crockford intended, except an arbitrary key/value:
JSON.parse = (text, fn) => {
const result = parse(text, fn)
if (result && result.arbitraryConfigField) {
result.arbiraryConfigField = 'arbitraryConfigValue'
}
return result
}
A better approach, but also prone to racing and could hinder regular JSON.parse performance.
(I'm wondering if performance-wise it wouldn't be better to just do a text.replace without any checking.)
Is there yet a third way to wait for the page to load, hook into it and on a subsequent reload (which, yes, necessarily re-renders everything) have the necessary value without the hassle and uncertainty of a race?
It would be grear to have this without the heavy artillery of designing a Chrome extension or hooking with Greasemonkey.

QWebEngineView show page only upon javascript script finish

I'm developing a C++ Qt application with a web view.
This is what I'm trying to do: I want to show a webpage (I don't have control on it, so I cannot modify it but I'm sure on what this page do, so don't worry for unexpected behavior) to the user, but only after running some javascript on it.
The problem is I'm not able to wait for the javascript.
I use Qt, QWebEngineView to load the page and to inject javascript file on it using:
void MainWindow::pageloaded(bool ok)
{
ui->webView->page()->runJavaScript(jQuery);
ui->webView->page()->runJavaScript(waitForKeyElement,[this](const QVariant &v) { qDebug() << v.toString();ui->webView->show();});
}
jquery is just the file jquery.min.js and waitForKeyElement is this and basically wait for elements of the page to appear in the DOM.
At the end of the latter file I appended my function:
waitForKeyElements (".x-live-elements", scrollpage);
function scrollpage (jNode) {
window.scrollTo(0, 80);
tables = document.getElementsByClassName("x-content");
table = tables[0];
table.style.backgroundColor="transparent";
document.body.style.backgroundColor = "transparent";
}
Basically when an element of the class .x-live-elements appears in the DOM, using the callback it scroll down the page and set background of a table and entire body to transparent. This is working, but I'd like to keep the page hidden until this is complete, now the user see the job running for a moment. At the beginning I call the method hide() for the webview, but how to call show() when the javascript is done ?
My idea was to add this at the end of waitForKeyElement.js file:
(function (){
while(true){
var bodyStyle = window.getComputedStyle(document.body, null);
var bgcolor = bodyStyle.backgroundColor;
var transparent = 'rgba(0, 0, 0, 0)';
if (bgcolor === transparent){
break;
}
}
a = "done";
return a;
})();
When the background of the page become transparent (this is basically the last javascript instruction I need) it means that the job is done, so the function should return and callback of runJavascript invoked.. but it doesn't work. It get stuck in the while loop forever and never return, the if condition is never met.
I know runJavascript is not synchronous, and the callback is fired soon as the javascript file reach the end, but with the while loop I should overcome this..
Any help ?
The best I was able to do was this:
Before running jquery and waitForKeyElement I hide the entire body
ui->webView->page()->runJavaScript("$('body').hide();");
ui->webView->page()->runJavaScript(jQuery);
ui->webView->page()->runJavaScript(waitForKeyElement);
Then in the waitForKeyElement.js my scrollpage function became:
function scrollpage (jNode) {
tables = document.getElementsByClassName("x-content");
table = tables[0];
table.style.backgroundColor="transparent";
document.body.style.backgroundColor = "transparent";
$('body').show();
window.scrollTo(0, 80);
}
This is of curse not the best solution, but is acceptable for my purpose.

Make NodeJS/JSDom wait for full rendering before scraping

I'm trying to scrape data from a website that I need to log into. Unfortunately, I'm getting different results using JSDom/NodeJS than I would if I were to use a web browser, such as FF. In particular, I'm not getting the log in form with the username, password and submit button.
I understand much of Javascript, at least, is asynchronous. However, I thought the "done" function of JSDom waits synchronously for the full rendering of the page. I guess what I'd like to do is simulate an HTTPS get and wait for the full document.ready to be done.
var jsdom = require("jsdom");
var jsdom_global = require("jsdom-global");
var fs = require("fs");
var jquery = fs.readFileSync("./jquery-3.1.1.min.js", "utf-8");
jsdom.env({
url: "https://wemc.smarthub.coop/Login.html#login:",
src: [jquery],
done: function (err, window) {
var $ = window.$;
if($("button#LoginSubmitButton").length) {
console.log('Click button found');
} else {
console.log('Click button not found');
}
// The following text boxes are not coming back:
// $("input#LoginUsernameTextBox")
// $("input#LoginPasswordTextBox")
// If I enable the line below, I see a lot less than I would if I
// do a view source in any reasonable browser.
//console.log($("body").html());
}
});
Usually, this will happen because JSDOM doesn't execute the JS when it hits the page. In that case, the only elements returned will be the server rendered HTML.
You could try a headless browser module such as PhantomJS etc and see how that goes for you. There's a section about the distinction between the two at the bottom of the JSDOM github page.

Any way to gracefully enforce a timeout limit when loading a slow external file via javascript?

I'm using javascript to include some content served up from a php file on another server. However, this other service can sometimes get flaky and either take a long time to load or will not load at all.
Is there a way in JS to try to get the external data for x number of seconds before failing and displaying a "please try again" message?
<script type="text/javascript" src="htp://otherserver.com/myscript.php"></script>
Couple issues: you can use timeout thresholds with XMLHttpRequest (aka ajax), but then since it's on an otherserver.com you cannot use XMLHttpRequest (and support all A-grade browsers) due to the Same Origin Policy restriction.
If the script introduces any kind of global name (eg any variable name, function name, etc) You can try setTimeout to keep checking for it:
var TIMELIMIT = 5; // seconds until timeout
var start = new Date;
setTimeout(function() {
// check for something introduced by your external script.
// A variable, namespace or function name here is adequate:
var scriptIncluded = 'otherServerVariable' in window;
if(!scriptIncluded) {
if ((new Date - start) / 1000 >= TIMELIMIT) {
// timed out
alert("Please try again")
}
else {
// keep waiting...
setTimeout(arguments.callee, 100)
}
}
}, 100)
The problem as I see it is you cannot cancel the request for the script. Please someone correct me if I'm wrong but removing the <script> from the DOM will still leave the browser's request for the resource active. So although you can detect that the script is taking longer than x seconds to load, you can't cancel the request.
I think you may be out of luck.
The only way I can think of doing this is to create a proxy on another (PHP-enabled) server which will fetch the data for you, but will stop when a certain timeout limit has been reached (and it can just return an empty result).
This is purely, purely theoretical:
<script> tags can be dynamically inserted into the DOM, at which point the script will be fetched and processed. This dynamic script tag injection is how some achieve cross-domain "AJAX."
I would imagine you could declare a global variable var hasLoaded = false;. At the end of the script you are attempting to load you could set that variable to true hadLoaded=true;. After injecting the script tag into the DOM you could then kickoff a setTimeout() whose callback function checks to see if "hasLoaded" is set to true. If it isn't, you can assume the script has not yet loaded fully into the browser. If it has, you can assume it has loaded completely.
Again, this is theoretical, but if you test it be sure to report back, I'm very curious.
I think that the only way to do this is take the content of the file via ajax and then set a timer. If the request finishes before the timer you can evaluate the respons with eval(that's not the better solution anyway), otherwise you can stop the ajax request and write the error message.

Javascript: Suppress "Stop running this script?", or how to use setTimeout?

I'm building a js library that reads binary files, including zip files.
Because there's no direct native support for arrays of binary data, when the zip files get large, there's a lot of copying that has to go on (See my other question for details).
This results in a "Stop Running this script?" alert. Now, I know this can happen if there's an infinite loop, but in my case, it's not an infinite loop. It's not a bug in the program. It just takes a loooooong time.
How can I suppress this?
This message is for security reason enabled, because otherwise you could simply block the users browser with a simple never ending loop. I think there no way to deactivate it.
Think about splitting you processing into serval parts, and schedule them via setTimeout, this should surpress the message, because the script is now not running all the time.
You could divide the process into increments, then use setTimeout to add a small delay.
In IE (and maybe Firefox too), the message is based on the number of statements executed, not the running time. If you can split some of the processing into a separate function, and defer it with setTimeout, I believe that it won't count toward the limit.
...answering my own question, so I could post the code I used.
The main issue was, I was reading the entire contents of a file, with a readToEnd() method, which actually reads one byte at a time. When reading a large file, it took a looooong time. The solution was to read asynchronously, and in batches.
This is the code I used:
readToEndAsync : function(callback) {
_state = "";
var slarge = "";
var s = "";
var txtrdr = this;
var readBatchAsync = function() {
var c = 0;
var ch = txtrdr.readChar();
while(ch != null)
{
s += ch;c++;
if(c > 1024)
{
slarge += s;
s = "";
break;
}
ch = txtrdr.readChar();
}
if (ch!=null){
setTimeout(readBatchAsync, 2);
}
else {
callback(slarge+s);
}
};
// kickoff
readBatchAsync();
return null;
},
And to call it:
textReader.readToEndAsync(function(out){
say("The callback is complete");
// the content is in "out"
});
I believe this feature is specific to Firefox and/or other browsers, and it has nothing to do with the javascript language itself.
As far as I know you (the programmer) have no way of stopping it in your visitors' browser.

Categories

Resources