QWebEngineView show page only upon javascript script finish - javascript

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.

Related

Redirecting URLS from page document text

So i am working on a Userscript and there is one major step i'm trying to find the easiest resolve with since i am very new to Javascript coding...I'm trying to perform/code a function that will open a specified URL:
EXAMPLE: Homepage ("http://www.EXAMPLE.com")
(page can be opened as 'Window.open' = Blank, or _self);
...when the parent or (current) URL that is open
EXAMPLE: innner.href = ("www.EXAMPLE.com/new/01262016/blah/blah/blah");
...has a text on the HTML documnt page that reads:
EXAMPLE TEXT from page ("www.EXAMPLE.com/new/01262016/blah/blah/blah");:
"this is the end of the page, please refresh to return back to homepage"
(TEXT: not the real keyword, but want to use phase as a detection for a setTimeout function to return back to home.)
Any help will be much appreicated, you guys are veryinformative here. Thanks in advance.
I think I have the gist of you question. It is a straighforward, though quite intensive, task to scan the entire text content of a page for specific keywords with JavaScript. However, if the keywords appear more than once (on multiple pages that should not redirect) then your users will get undesirable results.
A simple solution would be to add a class="last-page" attribute to the body-tag of the final page and run a function that checks for this. Something like....
HTML
<body class="last-page"><!--page content--></body>
JS
window.onload = function() {
var interval = 5000; // five seconds
if (document.body.classList.contains('last-page')) {
setTimeout(function() {
window.location.assign('http://the-next-page.com/');
}, interval);
}
};
Alternatively, if you have the ability to wrap the specified text in a uniquely identified html-tag, such as...
<span id="last-page">EXAMPLE TEXT</span>
...then the presence of this tag can be checked on each page load - similar to the function above:
window.onload = function() {
var interval = 5000;
if (document.getElementById('last-page') {
setTimeout(/* code as before */);
}
};
Yet another solution is to check the page URL against a variable...
window.onload = function() {
var finalURL = 'http://the-last-page.com/blah/...';
if (window.location === finalURL) {
/* same as before */
}
};
If this kind of thing is not an option please leave a comment and I'll add a function that gathers a pages entire text content and compares adjacent words to a pre-defined set of keys.

How to call a site's function from a Chrome extension?

I'm trying to make a Chrome extention for myself, so that when I visit any sort of channel at Twitch.tv, the chat will automatically hide.
I've been looking at it with Firebug and I found toggle_chat(). If I type that in the console, the chat is no longer visible.
In my userscript file, I have written
window.onload = function() {
toggle_chat();
}
but it says
Uncaught ReferenceError: toggle_chat is not defined" in the console when I load a Twitch channel.
Any ideas how to make this work?
This has nothing to do with timing. Chrome extensions and content scripts execute in an isolated world, meaning they have no access to the page's javascript including functions. You could make it so that your content script appends a <script> element that then calls the page function that you want but it would be far easier to just simulate a click on the #right_close element. You can do this with pure Javascript like this:
window.onload = function(){
var evObj = document.createEvent('Events');
evObj.initEvent('click', true, false);
document.querySelector('#right_close').dispatchEvent(evObj);
}
I know this is very hacky, but it gets the job done, and sometimes that exactly what you need. :) It'll check for the function roughly ever half second until it exists. When it's finally there, it'll call the function then clear the timer.
window.onload = function() {
var id = null;
var check = function() {
if (typeof toggle_chat === "function") {
toggle_chat();
clearInterval(id);
}
}
id = setInterval(check, 500);
}

Efficient scrolling of piped output in a browser window

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

Getting functions from another script in JS

I load this JS code from a bookmarklet:
function in_array(a, b)
{
for (i in b)
if (b[i] == a)
return true;
return false;
}
function include_dom(script_filename) {
var html_doc = document.getElementsByTagName('head').item(0);
var js = document.createElement('script');
js.setAttribute('language', 'javascript');
js.setAttribute('type', 'text/javascript');
js.setAttribute('src', script_filename);
html_doc.appendChild(js);
return false;
}
var itemname = '';
var currency = '';
var price = '';
var supported = new Array('www.amazon.com');
var domain = document.domain;
if (in_array(domain, supported))
{
include_dom('http://localhost/bklts/parse/'+domain+'.js');
alert(getName());
}
[...]
Note that the 'getName()' function is in http://localhost/bklts/parse/www.amazon.com/js. This code works only the -second- time I click the bookmarklet (the function doesn't seem to get loaded until after the alert()).
Oddly enough, if I change the code to:
if (in_array(domain, supported))
{
include_dom('http://localhost/bklts/parse/'+domain+'.js');
alert('hello there');
alert(getName());
}
I get both alerts on the first click, and the rest of the script functions. How can I make the script work on the first click of the bookmarklet without spurious alerts?
Thanks!
-Mala
Adding a <script> tag through DHTML makes the script load asynchroneously, which means that the browser will start loading it, but won't wait for it to run the rest of script.
You can handle events on the tag object to find out when the script is loaded. Here is a piece of sample code I use that seems to work fine in all browsers, although I'm sure theres a better way of achieving this, I hope this should point you in the right direction:
Don't forget to change tag to your object holding the <script> element, fnLoader to a function to call when the script is loaded, and fnError to a function to call if loading the script fails.
Bear in mind that those function will be called at a later time, so they (like tag) must be available then (a closure would take care of that normally).
tag.onload = fnLoader;
tag.onerror = fnError;
tag.onreadystatechange = function() {
if (!window.opera && typeof tag.readyState == "string"){
/* Disgusting IE fix */
if (tag.readyState == "complete" || tag.readyState == "loaded") {
fnLoader();
} else if (tag.readyState != "loading") {
fnError();
};
} else if (tag.readyState == 4) {
if (tag.status != 200) {
fnLoader();
}
else {
fnError();
};
};
});
It sounds like the loading of the external script (http://localhost/bklts/parse/www.amazon.com/js) isn't blocking execution until it is loaded. A simple timeout might be enough to give the browser a chance to update the DOM and then immediately queue up the execution of your next block of logic:
//...
if (in_array(domain, supported))
{
include_dom('http://localhost/bklts/parse/'+domain+'.js');
setTimeout(function() {
alert(getName());
}, 0);
}
//...
In my experience, if zero doesn't work for the timeout amount, then you have a real race condition. Making the timeout longer (e.g. 10-100) may fix it for some situations but you get into a risky situation if you need this to always work. If zero works for you, then it should be pretty solid. If not, then you may need to push more (all?) of your remaining code to be executed into the external script.
The best way I could get working: Don't.
Since I was calling the JS from a small loader bookmarklet anyway (which just tacks the script on to the page you're looking at) I modified the bookmarklet to point the src to a php script which outputs the JS code, taking the document.domain as a parameter. As such, I just used php to include the external code.
Hope that helps someone. Since it's not really an answer to my question, I won't mark this as the accepted answer. If someone has a better way, I'd love to know it, but I'll be leaving my code as is:
bookmarklet:
javascript:(function(){document.body.appendChild(document.createElement('script')).src='http://localhost/bklts/div.php?d='+escape(document.domain);})();
localhost/bklts/div.php:
<?php
print("
// JS code
");
$supported = array("www.amazon.com", "www.amazon.co.uk");
$domain = #$_GET['d']
if (in_array($domain, $supported))
include("parse/$domain.js");
print("
// more JS code
");
?>

Loading message

Searching for a js script, which will show some message (something like "Loading, please wait") until the page loads all images.
Important - it mustn't use any js framework (jquery, mootools, etc), must be an ordinary js script.
Message must disappear when the page is loaded.
Yeah an old-school question!
This goes back to those days when we used to preload images...
Anyway, here's some code. The magic is the "complete" property on the document.images collection (Image objects).
// setup a timer, adjust the 200 to some other milliseconds if desired
var _timer = setInterval("imgloaded()",200);
function imgloaded() {
// assume they're all loaded
var loaded = true;
// test all images for "complete" property
for(var i = 0, len = document.images.length; i < len; i++) {
if(!document.images[i].complete) { loaded = false; break; }
}
// if loaded is still true, change the HTML
if(loaded) {
document.getElementById("msg").innerHTML = "Done.";
// clear the timer
clearInterval(_timer);
}
};
Of course, this assumes you have some DIV thrown in somewhere:
<div id="msg">Loading...</div>
Just add a static <div> to the page, informing user that the page is loading. Then add window.onload handler and remove the div.
BTW, what’s the reason of this? Don’t users already have page load indicators in their browsers?
You should do async ajax requests for the images and add a call back when it's finished.
Here's some code to illustrate it:
var R = new XMLHttpRequest();
R.onreadystatechange = function() {
if (R.readyState == 4) {
// Do something with R.responseXML/Text ...
stopWaiting();
}
};
Theoretically you could have an onload event on every image object that runs a function that checks if all images is loaded. This way you don´t need a setTimeOut(). This would however fail if an image didn´t load so you would have to take onerror into account also.

Categories

Resources