window.location called too often? - javascript

I am trying to intercept the window.location changes to do some native work in Android app. To ve more specific, I overwrite the call in WebViewClient:
public boolean shouldOverrideUrlLoading(WebView view, String url)
to look for anything start with "native://". The JavaScript code is like this.
function callNative() {
window.location = "native://doSomeNativeWork()";
}
function callNativeManyTimes(count) {
for(i = 0 ; i < count ; i ++) {
callNative();
}
}
DoSomeNativeWork<br/>
The problem I am seeing is that if I call "window.location = something" many time very quickly (like in the code above) , I will only get one call inside the WebViewClient on the native code side. If I make the call 50ms apart, I will get everyone of them. I am thinking that the browser is doing some optimization around this.
I think I can solve this problem like this: do not use window.location, change to embed a native object to javascript, and call methods on that object in javascript. I am just wondering why this is happening. Can someone more familiar wit JS to share some insight?
Thanks

I had exact the same problem. You can do that by creating iframes. I create iframes with the native urls and then I delete the iframe after 2 seconds just so we don't have too many laying around on the DOM. Worked like a charm to me. You can create as many iframes as you want.
I also included a cache buster on the iframe url, even though I'm not sure it's needed. Better safe then sorry.
function callNative(url){
var _frame = document.createElement('iframe');
_frame.width=0; _frame.height=0;_frame.frameBorder=0;
document.body.appendChild(_frame);
if (url.indexOf('?') >= 0){
url = url + "&cb=";
}else{
url = url + "?cb=";
}
_frame.src = url + Math.round(Math.random()*1e16);
// Remove the iframe
setTimeout(function(){document.body.removeChild(_frame);}, 2000);
}
function callNativeManyTimes(count) {
for(i = 0 ; i < count ; i ++) {
callNative('native://doSomeNativeWork()');
}
}
DoSomeNativeWork<br/>
Note that I used the approach above on iOS. Not sure if it's exactly the same on Android, but from judging the question and other answer I guess it's the same.

I would guess that loading a URL is asynchronous (it would probably be a bad idea to block the execution of a script until a URL has been resolved). As such, setting window.location will presumably only queue up the loading of the new URL, which will be done in a different thread.
Waiting 50ms is a hack that may or may not work. You need to find a different approach. You need something that guarantees that each one of those URLs will be resolved. If the order doesn't matter, you could just use images, like somebody suggested. Otherwise, you could use a native JavaScript interface (which is probably the better approach).

Related

Best (and secure) way to call javascript function (with arguments) using a url from another page

After reviewing dozens of Stack Overflow posts, I'm thoroughly confused. What I am trying to do is create a URL through an tag on one page that would open another webpage and run a function that requires two arguments. I thought this would be simple but I keep seeing references to "cross site scripting vulnerabilities" and I am not familiar with this potential security problem and feel like I am now playing with fire. I do not want to utilize something — even if the code works — if it opens up security risks. Could someone point me in the right direction with the correct (and most secure) way to do this? I can do my research (and learning) from there. Much appreciated.
For example you can append some parameter at the end of your URL
https://your-url/?parameter=hello
When this URL is opened on another webpage you can run JavaScript or a PHP function based on that URL query.
For JavaScript
getUrlParam(slug) {
let url = new URL(window.location);
let params = new URLSearchParams(url.search);
let param = params.get(slug);
if (param) {
return param;
} else {
return false;
}
}
console.log(getUrlParam('parameter'));
After that, you can run this function to check if any parameter is passed in that URL or not.
If this function returns that's given slug parameter you can run your custom code inside that if condition block

Will javascript loop make my page get eventually stuck?

i have this function:
<script language="javascript">
function live(){
var d = $live;
var elm = document.getElementById("live");
if(d==1){
elm.style.display = 'block';
} else{
elm.style.display = 'none';
}
}
</script>
setInterval(function(){live();},10000);
and im just concerned about my page getting stuck after having it open on the browser for a while or causing my users browser to stop responding or anything like that. How safe is to use loops like this?
Is this what google or facebook use to show new notifications alerts on their page in real time? That seems to go pretty smoothly.
Thank you.
This isn't a loop in the traditional sense, it's really just a function which is called at a regular interval, so you are in the clear here. Just be careful that nothing increases the memory use each time it executes, as that is what will most likely be what will kill the user's browser.
Also, the setInterval needs to me in a script tag, otherwise it will show up on your page.
Use of setInterval is a common practice for showing notifications on websites. It wont hang your page, although you must clear the interval once it is no longer required. Say you have already shown the notification, so better hold the reference of setInterval so that you could clear it later.
var ref = setInterval(fn, 100);
clearInterval(ref);

How PhoneGap keep Javascript executing after changing window.location/document.location

According to this question, when we set the window.location, javascript will "stop" executing or turn into a race condition.
Sometimes we need to fire window.location = SOMESCH://xxx multiple times inside a WebView to send "Notifications" back to our app. For example setting window.location = myapp://loginButtonEnabled?e=1 to tell the app that the user had filled in some nessasary info and can start login. It seems to be impossible to do something like this:
function(){
window.location = myapp://loginButtonEnable?e=1;
window.location = myapp://hideHintView;
.....
window.location = myapp://theLastThing;
}
Only the last window.location = myapp://theLastThing will be fired and then the execution of Javascript will stop(though we stopped the redirecting in our app by returning NO in webView:shouldStartLoadWithRequest:navigationType:).
I found it interesting that PhoneGap made this possible by using a dispatching queue, but I still haven't figure out why it works, anybody knows the trick??
BTW, is there a simple way to "resume" the execution after setting location? It will be much better than using an operation queue.
You need to give the event loop a chance to respond to the location change each time. The typical way of doing this is using a setTimeout with a small/zero delay, which has the effect of moving execution to the next event loop tick. Try something like this:
var q=[];
function dequeue() {
window.location='myapp://'+q.shift();
if (q.length>0) setTimeout(dequeue,0);
}
function notifyApp(cmd) {
q.push(cmd);
if (q.length==1) setTimeout(dequeue,0);
}
notifyApp('loginButtonEnable?e=1');
notifyApp('hideHintView');
notifyApp('theLastThing');
As for javascript execution stopping, setting window.location shouldn't have this effect unless it actually results in a page change - perhaps try using the technique above and see if your javascript continues after the last notifyApp() call.
EDIT: Another approach to this issue is to create temporary iframes instead of changing the location of the current page - see for example
Triggering shouldStartLoadWithRequest with multiple window.location.href calls

Chrome JavaScript location object

I am trying to start 3 applications from a browser by use of custom protocol names associated with these applications. This might look familiar to other threads started on stackoverflow, I believe that they do not help in resolving this issue so please dont close this thread just yet, it needs a different approach than those suggested in other threads.
example:
ts3server://a.b.c?property1=value1&property2=value2
...
...
to start these applications I would do
location.href = ts3server://a.b.c?property1=value1&property2=value2
location.href = ...
location.href = ...
which would work in FF but not in Chrome
I figured that it might by optimizing the number of writes when there will be effectively only the last change present.
So i did this:
function a ()
{
var apps = ['ts3server://...', 'anotherapp://...', '...'];
b(apps);
}
function b (apps)
{
if (apps.length == 0) return;
location.href = apps[0]; alert(apps[0]);
setTimeout(function (rest) {return function () {b(rest);};} (apps.slice(1)), 1);
}
But it didn't solve my problem (actually only the first location.href assignment is taken into account and even though the other calls happen long enough after the first one (thanks to changing the timeout delay to lets say 10000) the applications do not get started (the alerts are displayed).
If I try accessing each of the URIs separately the apps get started (first I call location.href = uri1 by clicking on one button, then I call location.href = uri2 by clicking again on another button).
Replacing:
location.href = ...
with:
var form = document.createElement('form');
form.action = ...
document.body.appendChild(form);
form.submit();
does not help either, nor does:
var frame = document.createElement('iframe');
frame.src = ...
document.body.appendChild(frame);
Is it possible to do what I am trying to do? How would it be done?
EDIT:
a reworded summary
i want to start MULTIPLE applications after one click on a link or a button like element. I want to achieve that with starting applications associated to custom protocols ... i would hold a list of links (in each link there is one protocol used) and i would try to do "location.src = link" for all items of the list. Which when used with 'for' does optimize to assigning only once (the last value) so i make the function something like recursive function with delay (which eliminates the optimization and really forces 3 distinct calls of location.src = list[head] when the list gets sliced before each call so that all the links are taken into account and they are assigned to the location.src. This all works just fine in Mozilla Firefox, but in google, after the first assignment the rest of the assignments lose effect (they are probably performed but dont trigger the associated application launch))
Are you having trouble looping through the elements? if so try the for..in statement here
Or are you having trouble navigating? if so try window.location.assign(new_location);
[edit]
You can also use window.location = "...";
[edit]
Ok so I did some work, and here is what I got. in the example I open a random ace of spades link. which is a custom protocol. click here and then click on the "click me". The comments show where the JSFiddle debugger found errors.

The definitive best way to preload images using JavaScript/jQuery?

I'm fully aware that this question has been asked and answered everywhere, both on SO and off. However, every time there seems to be a different answer, e.g. this, this and that.
I don't care whether it's using jQuery or not - what's important is that it works, and is cross-browser.]
So, what is the best way to preload images?
Unfortunately, that depends on your purpose.
If you plan to use the images for purposes of style, your best bet is to use sprites.
http://www.alistapart.com/articles/sprites2
However, if you plan to use the images in <img> tags, then you'll want to pre-load them with
function preload(sources)
{
var images = [];
for (i = 0, length = sources.length; i < length; ++i) {
images[i] = new Image();
images[i].src = sources[i];
}
}
(modified source taken from What is the best way to preload multiple images in JavaScript?)
using new Image() does not involve the expense of using DOM methods but a new request for the image specified will be added to the queue. As the image is, at this point, not actually added to the page, there is no re-rendering involved. I would recommend, however, adding this to the end of your page (as all of your scripts should be, when possible) to prevent it from holding up more critical elements.
Edit: Edited to reflect comment quite correctly pointing out that separate Image objects are required to work properly. Thanks, and my bad for not checking it more closely.
Edit2: edited to make the reusability more obvious
Edit 3 (3 years later):
Due to changes in how browsers handle non-visible images (display:none or, as in this answer, never appended to the document) a new approach to pre-loading is preferred.
You can use an Ajax request to force early retrieval of images. Using jQuery, for example:
jQuery.get(source);
Or in the context of our previous example, you could do:
function preload(sources)
{
jQuery.each(sources, function(i,source) { jQuery.get(source); });
}
Note that this doesn't apply to the case of sprites which are fine as-is. This is just for things like photo galleries or sliders/carousels with images where the images aren't loading because they are not visible initially.
Also note that this method does not work for IE (ajax is normally not used to retrieve image data).
Spriting
As others have mentioned, spriting works quite well for a variety of reasons, however, it's not as good as its made out to be.
On the upside, you end up making only one HTTP request for your images. YMMV though.
On the down side you are loading everything in one HTTP request. Since most current browsers are limited to 2 concurrent connections the image request can block other requests. Hence YMMV and something like your menu background might not render for a bit.
Multiple images share the same color palette so there is some saving but this is not always the case and even so it's negligible.
Compression is improved because there is more shared data between images.
Dealing with irregular shapes is tricky though. Combining all new images into the new one is another annoyance.
Low jack approach using <img> tags
If you are looking for the most definitive solution then you should go with the low-jack approach which I still prefer. Create <img> links to the images at the end of your document and set the width and height to 1x1 pixel and additionally put them in a hidden div. If they are at the end of the page, they will be loaded after other content.
As of January 2013 none of the methods described here worked for me, so here's what did instead, tested and working with Chrome 25 and Firefox 18. Uses jQuery and this plugin to work around the load event quirks:
function preload(sources, callback) {
if(sources.length) {
var preloaderDiv = $('<div style="display: none;"></div>').prependTo(document.body);
$.each(sources, function(i,source) {
$("<img/>").attr("src", source).appendTo(preloaderDiv);
if(i == (sources.length-1)) {
$(preloaderDiv).imagesLoaded(function() {
$(this).remove();
if(callback) callback();
});
}
});
} else {
if(callback) callback();
}
}
Usage:
preload(['/img/a.png', '/img/b.png', '/img/c.png'], function() {
console.log("done");
});
Note that you'll get mixed results if the cache is disabled, which it is by default on Chrome when the developer tools are open, so keep that in mind.
In my opinion, using Multipart XMLHttpRequest introduced by some libraries will be a preferred solution in the following years. However IE < v8, still don't support data:uri (even IE8 has limited support, allowing up to 32kb). Here is an implementation of parallel image preloading - http://code.google.com/p/core-framework/wiki/ImagePreloading , it's bundled in framework but still worth taking a look.
This was from a long time ago so I dont know how many people are still interested in preloading an image.
My solution was even more simple.
I just used CSS.
#hidden_preload {
height: 1px;
left: -20000px;
position: absolute;
top: -20000px;
width: 1px;
}
Here goes my simple solution with a fade in on the image after it is loaded.
function preloadImage(_imgUrl, _container){
var image = new Image();
image.src = _imgUrl;
image.onload = function(){
$(_container).fadeTo(500, 1);
};
}
For my use case I had a carousel with full screen images that I wanted to preload. However since the images display in order, and could take a few seconds each to load, it's important that I load them in order, sequentially.
For this I used the async library's waterfall() method (https://github.com/caolan/async#waterfall)
// Preload all images in the carousel in order.
image_preload_array = [];
$('div.carousel-image').each(function(){
var url = $(this).data('image-url');
image_preload_array.push(function(callback) {
var $img = $('<img/>')
$img.load(function() {
callback(null);
})[0].src = url;
});
});
async.waterfall(image_preload_array);
This works by creating an array of functions, each function is passed the parameter callback() which it needs to execute in order to call the next function in the array. The first parameter of callback() is an error message, which will exit the sequence if a non-null value is provided, so we pass null each time.
See this:
http://www.mattfarina.com/2007/02/01/preloading_images_with_jquery
Related question on SO:
jquery hidden preload

Categories

Resources