In mobile safari on iPhone or iPod Touch if a user clicks on a mailto link and then returns to the page (either send or cancel), timers no longer function inside of javascript. I've posted a bug to apple, and on openradar.
However, I was wondering if anyone out there has come across this before and come up with some sort of workaround.
update: Here is some sample code
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>Testing</title>
</head>
<body>
<h1 id="normal">Normal: 0</h1>
<h1 id="timed">Timed: 0</h1>
<h1 id="interval">Interval: 0</h1>
<a id="clicker">Click Me</a><br />
Mail To
<script type="text/javascript">
window.addEventListener('load', function ()
{
var count = 0;
var interval = 0;
var id;
document.getElementById('clicker').addEventListener('click', function () {
interval = 0;
count++;
document.getElementById('normal').innerHTML = 'Normal: ' + count;
setTimeout(function () {document.getElementById('timed').innerHTML = 'Timed: ' + count; }, 100);
id = setInterval(function ()
{
interval++;
if(interval > 5)
{
clearInterval(id);
return;
}
document.getElementById('interval').innerHTML = 'Interval: ' + interval;
}, 200);
}, false);
}, false);
</script>
</body>
</html>
To clear up some apparent confusion about what I'm saying is a bug, its not that when you leave the page and return any timers that had been running have stopped, this is to be expected. The problem is that once a user returns to the page if you start new timers, they will never fire.
That' not a bug because the browser windows is actually closed when the Mail application launches. When the user returns, it's restored but any scripts that were running will have halted.
Though this is not very nice, it's expected.
Related
So I am embedding a video into a custom iframe, and I'm not using youtube, vimeo or any of those so I can't use their APIs. I am making an idle-timer for it, so when the user hasnt acted in X amount of time, it will bring up a confirm window asking if they want to keep watching or restart. However, while this window is up, I want the video to pause, which is proving surprisingly difficult. It also pretty much needs to be cross-domain as I will be serving the videos with an s3 bucket.
I have seen many threads saying this is basically not possible, but I find that hard to believe. Is it true?
Here's my code (the main part I need help with is pauseVideo() near the bottom):
<!DOCTYPE html>
<html lang="en" dir="ltr">
<head>
<meta charset="utf-8">
<title>HRMSC</title>
</head>
<body>
<iframe class="iframe" id="primaryVideo" src="amazon-s3-video-link.mp4"
width="1000"
height="562.5">
<p> Your browser does not support iframes. </p>
</iframe>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js">
</script>
<script type="text/javascript" src="./IdleScript.js">
</script>
</body>
</html>
IdleScript.js :
var idleTime = 0;
var clickIframe = window.setInterval(checkFocus, 100);
var idleInterval = setInterval(timerIncrement, 600); // 1 second
var i = 0;
function checkFocus() {
if(document.activeElement == document.getElementById("primaryVideo")) {
idleTime = 0;
console.log("clicked "+(i++));
$('#primaryVideo').blur();
}
}
function timerIncrement() {
idleTime = idleTime + 1;
if (idleTime > 5) { // seconds
console.log("restart?");
if (this.resetInterstitial()){
idleTime = 0;
window.location.reload();
}
else{
idleTime = 0;
console.log("keep watching");
}
}
}
var pauseVideo = function ( element ) {
// WHAT CAN I DO HERE?
console.log("pause!");
// WHAT CAN I DO HERE?
};
function resetInterstitial(){
pauseVideo(primaryVideo);
return confirm("You haven't tapped anything in a while. Do you want to keep watching or start from the beginning?");
}
Use a <video>-tag: https://www.w3schools.com/tags/tag_video.asp
and use the build-in javascript functions https://www.w3schools.com/tags/av_met_pause.asp
Continuing this question: Task manager shows memory leak, but Heap snapshot doesn't
I managed to create a very simple example, which illustrates this leak, here is full source code:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>svg test</title>
<script type="text/javascript">
var svg;
var interval;
var svg;
window.onload = function(){
createSVG();
start();
}
function start(){
interval = setInterval(createElements, 100);
}
function createSVG(){
var div = document.getElementById("svgdiv");
div.innerHTML = "";
svg = createSvgElement("svg");
svg.style.position = "absolute";
svg.style.width = "600px";
svg.style.height = "500px";
svg.setAttribute("version", "1.1");
div.appendChild(svg);
createElements();
}
function createElements(){
removeElements();
for(var i = 0; i < 500; i++){
var element = createSvgElement("circle");
element.setAttribute("r", Math.random() * 10);
var transform = "translate(" + Math.round(Math.random() * 600) + "," + Math.round(Math.random() * 500) + ")";
element.setAttribute("transform", transform);
element.setAttribute("fill", "#CC0000");
svg.appendChild(element);
}
}
function removeElements(){
while(svg.hasChildNodes() ){
svg.removeChild(svg.lastChild);
}
}
function createSvgElement (name) {
return document.createElementNS("http://www.w3.org/2000/svg", name);
}
function stop(){
clearInterval(interval)
}
</script>
</head>
<body style="background-color:#FFFFFF">
<div id="svgdiv" style="width:600px; height:500px;"></div>
<input type="button" value="start" onclick="start()">
<input type="button" value="stop" onclick="stop()">
</body>
</html>
If I run this script, Chrome keeps eating the memory until it crashes. Other browsers don't. Instead of removing children one by one I also tried to clear it in a fast way by setting innerHTML="", but it's the same.
I enabled an experimental feature of Chrome which shows the kind of memory is used. The "Pages structures" memory is increasing a bit (however the HTML remains the same and there are no detached DOM objects), but the most memory goes to "Other" section.
If I stop the script and force GC to do it's job, the memory decreases by only few kilobytes. However if I wait for a minute or two, the memory is cleaned almost to a initial level. I know that my script is quite intense, but this happens also if I run it every 1 or two seconds only - I think that's quite enough for GC to do it's job. And I know GC is working, as other kind of memory is released. Could this be a Chrome bug? Maybe I should do something before removing the elements?
I managed to slow it down but it is still leaking. I also recreated the test using Raphael.js which did not leak. But when I was testing I found that it was when it was appending the circles. So Raphael must be doing something to stop it from leaking at that point.
Not sure, if it would be of much help, but if you define "var svg" only once,
then memory-consumption ( in Task manager) is not rising that fast
and also stop() function does work after that
This was indeed bug in Chrome, which was fixed some time later:
https://bugs.chromium.org/p/chromium/issues/detail?id=172221&can=4&q=svg&colspec=ID%20Pri%20Mstone%20ReleaseBlock%20OS%20Area%20Feature%20Status%20Owner%20Summary
I have a loop which needs to be run 200 million times in a browser. It's a simulator which several people need to use regularly. It takes about 15 minutes to run, but during this time, the browsers will frequently pop up a warning with "this script is taking too long" etc., and it completely hangs Firefox during the function. This also means the page does not update my status indicator (which is just a number).
I have googled "javascript yield" and read the first 4 pages of hits. Some discuss a new "yield" keyword, but there is only one description and example, which I find incomprehensible, e.g. "The function containing the yield keyword is a generator. When you call it, it's formal parameters are bound to actual arguments, but it's body isn't actually evaluated". Does yield yield to the UI?
One of the few solutions I did find is this old post which uses the deprecated callee argument and a timer to call itself:
http://www.julienlecomte.net/blog/2007/10/28/
However, the above example doesn't contain any loop variables or state, and when I add these it falls apart, and my net result is always zero.
It also doesn't do chunking, but I have found some other examples which chunk using "index % 100 == 0" on every iteration. However, this seems to be a slow way of doing it. E.g. this:
How to stop intense Javascript loop from freezing the browser
But it doesn't have any way to update progress, and doesn't yield to the UI (so still hangs the browser). Here is a test version which hangs the browser during execution:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<script>
var spins = 1000000
var chunkSize = 1000;
var chunk;
function Stats() {this.a=0};
var stats = new Stats();
var big;
var index = 0;
var process = function() {
for (; index < spins; index++) {
stats.a++;
big = (big/3.6)+ big * 1.3 * big / 2.1;
console.write(big);
// Perform xml processing
if (index + 1 < spins && index % 100 == 0) {
document.getElementById("result").innerHTML = stats.a;
setTimeout(process, 5);
}
}
document.getElementById("result").innerHTML = stats.a;
};
</script>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>Untitled Document</title>
</head>
<body onload="process()">
<div id=result>result goes here.</div>
</body>
</html>
and here is another attempt which the stats.a is always zero (So I presume there is some scoping issue):
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<script>
var spins = 1000000
var chunkSize = 1000;
var chunk;
function Stats() {this.a=0};
var stats = new Stats();
function doIt() {
function spin() {
for (spinIx=0; (spinIx<chunkSize) && (spinIx+chunk < spins); spinIx++) {
stats.a++;
}
}
for (chunk =0; chunk < spins; chunk+=chunkSize){
setTimeout(spin, 5);
document.getElementById("result").innerHTML = stats.a;
}
document.getElementById("result").innerHTML = stats.a;
}
</script>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>Untitled Document</title>
</head>
<body onload="doIt()">
<div id=result>result goes here.</div>
</body>
</html>
I've spent 48 hours trying to get this working - either I am very dumb or this is very hard. Any ideas?
Several people have suggested web workers. I tried several days to get his working, but I could not find a similar example which passes a number etc. The code below was my last attempt to get it working, but the result is always 0 when it should be 100000. I.e. it fails in the same way that my second example above fails.
spinMaster.html:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
</head>
<body>
<script>
if(typeof(Worker)==="undefined") {
document.write("<h1>sorry, your browser doesnt support web workers, please use firefox, opera, chorme or safari</h1>");
}
var worker =new Worker("spinWorker.js");
worker.postMessage({times:1000000});
worker.onmessage=function(event){
document.getElementById("result").innerHTML=event.data;
};
</script>
<div id="result">result goes here</div>
</body>
</html>
spinWorker.js
function State() {
this.a=0;
}
var state = new State();
self.addEventListener('message', spin, false);
function spin(e) {
var times, i;
times = e.data.times;
//times = 1000000; // this doesnt work either.
for(i;i<times;i++) {
state.a++;
}
self.postMessage(state.a);
}
resultant output: 0
Web workers sound like the better solution.
I wrote this up quickly so i dunno if itll work. Performance will be very bad...
Edit: Changed to same format as poster. Tested
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<script>
var spins = 1000000
var chunkSize = 1000;
var chunk;
function Stats() {this.a=0};
var stats = new Stats();
var big = 0.0;
var index = 0;
function workLoop() {
index += 1;
stats.a++;
big = (big/3.6)+ big * 1.3 * big / 2.1;
console.log(big);
// Perform xml processing
document.getElementById('result').innerHTML = stats.a;
if (index < spins) {
setTimeout(workLoop, 5);
}
}
</script>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>Untitled Document</title>
</head>
<body onload="workLoop()">
<div id="result">result goes here.</div>
</body>
</html>
since JS is single threaded usually, I don't think there is any way around it. If you are supporting only newer browsers however, you might want to look into web workers, which can spawn a new thread to do work: http://developer.mozilla.org/en-US/docs/DOM/Using_web_workers
the only downside I can think of is that I've read it is hard to debug Web Workers because dev tools (Chrome Dev tools unless running dev channel, firebug), doesn't support profiling for it.
here's a nice tutorial to get you started: http://net.tutsplus.com/tutorials/javascript-ajax/getting-started-with-web-workers/
You have close to a working example in your 1st test, but I do see a logic error. In your if(), you need to return from the function otherwise it'll always be running multiple functions competing for that thread.
var process = function() {
for (; index < spins; index++) {
stats.a++;
big = (big/3.6)+ big * 1.3 * big / 2.1;
console.write(big);
// Perform xml processing
if (index + 1 < spins && index % 100 == 0) {
document.getElementById("result").innerHTML = stats.a;
setTimeout(process, 5);
//!!!!!
return;//without this it'll keep iterating through the for loop without waiting for the next 5ms
//!!!!!
}
}
document.getElementById("result").innerHTML = stats.a;
};
Neither of the samples as-is wait for the next setTimeout (in your 2nd Example you continue iterating through your for loop until the for loop is completed, but at each block size you set a timeout for a subsequent iteration. All this does is delay the entire sequence for 5 ms, they still all pileup and execute 5 ms from when your for loop begins iterating)
All in all you seem to be on the right track, there are just small logic errors in both examples.
There is a library on github available for this type of big looping without locking the browser/thread and without using web-workers.
Somewhat experimental, and supports big recursive loops, but it may work for you. Depends on q.js for resolving promises.
https://github.com/sterpe/stackless.js
This should do what you want:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<script>
var spins = 1000000
var chunkSize = 1000;
var stats = {a:0,spins:0};
function doIt() {
for (var chunk = 0; chunk < chunkSize; chunk++) {
stats.a++;
}
document.getElementById("result").innerHTML = stats.a;
if(++stats.spins < spins) setTimeout(doIt,5);
}
</script>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>Untitled Document</title>
</head>
<body onload="doIt()">
<div id=result>result goes here.</div>
</body>
</html>
I have been working with the demo provided at http://code.google.com/apis/ajax/playground/?exp=youtube#chromeless_player. What I am wondering is if there is a way to make the player HTML5 or compatible with ios devices. I can't seem to figure out how I would do that? I don't think the code works out of the box as I have tried it from my iphone and it doesn't seem to work... The biggest reason I am using this is because I need to be able to adjust volume and get the video event changes...
<!--
You are free to copy and use this sample in accordance with the terms of the
Apache license (http://www.apache.org/licenses/LICENSE-2.0.html)
-->
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="content-type" content="text/html; charset=utf-8"/>
<title>YouTube Player API Sample</title>
<style type="text/css">
#videoDiv {
margin-right: 3px;
}
#videoInfo {
margin-left: 3px;
}
</style>
<script src="http://www.google.com/jsapi" type="text/javascript"></script>
<script type="text/javascript">
google.load("swfobject", "2.1");
</script>
<script type="text/javascript">
/*
* Chromeless player has no controls.
*/
// Update a particular HTML element with a new value
function updateHTML(elmId, value) {
document.getElementById(elmId).innerHTML = value;
}
// This function is called when an error is thrown by the player
function onPlayerError(errorCode) {
alert("An error occured of type:" + errorCode);
}
// This function is called when the player changes state
function onPlayerStateChange(newState) {
updateHTML("playerState", newState);
}
// Display information about the current state of the player
function updatePlayerInfo() {
// Also check that at least one function exists since when IE unloads the
// page, it will destroy the SWF before clearing the interval.
if(ytplayer && ytplayer.getDuration) {
updateHTML("videoDuration", ytplayer.getDuration());
updateHTML("videoCurrentTime", ytplayer.getCurrentTime());
updateHTML("bytesTotal", ytplayer.getVideoBytesTotal());
updateHTML("startBytes", ytplayer.getVideoStartBytes());
updateHTML("bytesLoaded", ytplayer.getVideoBytesLoaded());
updateHTML("volume", ytplayer.getVolume());
}
}
// Allow the user to set the volume from 0-100
function setVideoVolume() {
var volume = parseInt(document.getElementById("volumeSetting").value);
if(isNaN(volume) || volume < 0 || volume > 100) {
alert("Please enter a valid volume between 0 and 100.");
}
else if(ytplayer){
ytplayer.setVolume(volume);
}
}
function playVideo() {
if (ytplayer) {
ytplayer.playVideo();
}
}
function pauseVideo() {
if (ytplayer) {
ytplayer.pauseVideo();
}
}
function muteVideo() {
if(ytplayer) {
ytplayer.mute();
}
}
function unMuteVideo() {
if(ytplayer) {
ytplayer.unMute();
}
}
// This function is automatically called by the player once it loads
function onYouTubePlayerReady(playerId) {
ytplayer = document.getElementById("ytPlayer");
// This causes the updatePlayerInfo function to be called every 250ms to
// get fresh data from the player
setInterval(updatePlayerInfo, 250);
updatePlayerInfo();
ytplayer.addEventListener("onStateChange", "onPlayerStateChange");
ytplayer.addEventListener("onError", "onPlayerError");
//Load an initial video into the player
ytplayer.cueVideoById("ylLzyHk54Z0");
}
// The "main method" of this sample. Called when someone clicks "Run".
function loadPlayer() {
// Lets Flash from another domain call JavaScript
var params = { allowScriptAccess: "always" };
// The element id of the Flash embed
var atts = { id: "ytPlayer" };
// All of the magic handled by SWFObject (http://code.google.com/p/swfobject/)
swfobject.embedSWF("http://www.youtube.com/apiplayer?" +
"version=3&enablejsapi=1&playerapiid=player1",
"videoDiv", "480", "295", "9", null, null, params, atts);
}
function _run() {
loadPlayer();
}
google.setOnLoadCallback(_run);
</script>
</head>
<body style="font-family: Arial;border: 0 none;">
<table>
<tr>
<td><div id="videoDiv">Loading...</div></td>
<td valign="top">
<div id="videoInfo">
<p>Player state: <span id="playerState">--</span></p>
<p>Current Time: <span id="videoCurrentTime">--:--</span> | Duration: <span id="videoDuration">--:--</span></p>
<p>Bytes Total: <span id="bytesTotal">--</span> | Start Bytes: <span id="startBytes">--</span> | Bytes Loaded: <span id="bytesLoaded">--</span></p>
<p>Controls: Play | Pause | Mute | Unmute</p>
<p><input id="volumeSetting" type="text" size="3" /> <- Set Volume | Volume: <span id="volume">--</span></p>
</div>
</td></tr>
</table>
</body>
</html>
As of today (2-28-2012) there is no way to play video in-line within a browser on iOS devices. You can only proide links that will either play the video in the iOS video player, or in the youtube app. The Youtube HTML5 player does this. On HTML5 enabled devices that do not support flash, it will serve an HTML5 tag.
March 7 is some big announcement for iOS, so maybe this will change then. But I do not expect it will as this is a long-standing way of operating on iOS devices.
EDIT - Here's how to do it.
To make this work for iOS, use the new HTML5 youtube player. Instructions and code found here in the YouTube API Blog:
http://apiblog.youtube.com/2010/07/new-way-to-embed-youtube-videos.html
I have a simple html page with some javascript where if i click a link, it will show a flash video in a div using the swfobject.embedSWF function.
I did a test:
Uninstalled flash from my machine, then reloaded the page and clicked the link...I correctly saw no video.
I then installed flash and came back to the same page (no reload) and the embed still won't work.
I know in swfobject 1.5 (I'm now using 2.2), I would be able to embed the swf after the user installs flash, but now the user needs to reload the page in order to get the flash to appear. This is not good for my current situation, anyone know what is going on here?
Here is sample code using a youtube video:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<title>SWFOBJECT TEST</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
<script src='https://ajax.googleapis.com/ajax/libs/jquery/1.4.3/jquery.min.js' type='text/javascript'></script>
<script src='http://ajax.googleapis.com/ajax/libs/swfobject/2.2/swfobject.js' type='text/javascript'></script>
<style>
#link{
display:block;
margin-bottom:10px;
}
#videoContainer{
width:610px;
height:360px;
}
</style>
<script type="text/javascript">
$(document).ready(function(){
$("#link").click(function(){
var flashVars = {};
var params = {};
flashVars["lang"] = "en";
params["menu"] = "false";
params["wmode"] = "opaque";
params["allowScriptAccess"] = "always";
params["allowFullScreen"] = "true";
params["bgcolor"] = "#fff";
//add video
swfobject.embedSWF("http://www.youtube.com/v/uZ0LL1SJ-6U?enablejsapi=1&playerapiid=ytplayer", "videoContainer",640, 480, "9.0.0", null, flashVars, params,{});
});
});
</script>
</head>
<body>
Click me
<div id="videoContainer"></div>
</body>
</html>
If you take your swfEmbed call out of the jquery, it works. I didn't really delve in to far, but I did this:
Click me
I made a new function called replaceDiv, which basically just does everything you are already doing in your jquery click function.
function replaceDiv () {
var flashVars = {};
var params = {};
flashVars["lang"] = "en";
params["menu"] = "false";
params["wmode"] = "opaque";
params["allowScriptAccess"] = "always";
params["allowFullScreen"] = "true";
params["bgcolor"] = "#fff";
//add video
swfobject.embedSWF("http://www.youtube.com/v/uZ0LL1SJ-6U?enablejsapi=1&playerapiid=ytplayer", "videoContainer",640, 480, "9.0.0", null, flashVars, params,{})
}
I then removed all of your existing JS and VOILA- works every time.
Let me know if you want me to actually try to debug your JQuery; I might add that it looks perfectly fine. You may be needing something else, I don't know.
-A
ps. Squarepusher is a BEAST. :P