I need help in resolving a memory leak found using JavaScript. What the program does is auto scroll the videos and when it reaches the center (supposed to be a grid but for the purposes of this problem, it is not visibily shown but can be noticed immediately) or scrolls off the screen, it will chop the video into two separate videos and put one chopped video on one side of the area and the other chopped video on the other side of the area.
Here is the JavaScript code for the program:
window.addEventListener("DOMContentLoaded", function()
{
var patternNumbers = new RegExp("[0-9]+", "g");
var videoCoordinates = {};
for (var i = 1; i <= 4; i++)
{
var videoElement = document.createElement("video");
var videoSource = document.createElement("source");
var videoId = "video-" + i;
videoCoordinates[videoId] = {};
videoCoordinates[videoId]["latitude"] = (Math.random() * 180 - 90).toFixed(8);
videoCoordinates[videoId]["longitude"] = (Math.random() * 360 - 180).toFixed(8);
videoElement.id = videoId;
videoElement.preload = "auto";
videoElement.setAttribute("webkit-playsinline", "");
if (((i - 1) % 4) < 2)
videoElement.style.left = ((i - 1) % 4) * 50 / 4 + "%";
else
videoElement.style.left = 50 + ((i - 1) % 4) * 50 / 4 + "%";
videoElement.style.top = Math.floor((i - 1) / 4) * 100 / Math.ceil(25 / 4) + "%";
videoElement.style.width = "2px";
videoElement.style.height = "1px";
videoSource.src = "test" + (((i - 1) % 5) + 1) + ".mp4";
videoSource.type = "video/mp4";
document.body.appendChild(videoElement);
videoElement.appendChild(videoSource);
videoElement.onclick = function()
{
var videoElement = this;
hiddenFunction(videoElement);;
}
videoElement.addEventListener("loadedmetadata", function()
{
var videoElement = this;
videoElement.style.width = Math.min(50 / 4, ((50 / 4) / 100 * window.innerHeight * videoElement.videoWidth / videoElement.videoHeight) / window.innerWidth * 100) + "%";
videoElement.style.height = (Math.min(50 / 4, ((50 / 4) / 100 * window.innerHeight * videoElement.videoWidth / videoElement.videoHeight) / window.innerWidth * 100) / 100 * window.innerWidth * videoElement.videoHeight / videoElement.videoWidth) / window.innerHeight * 100 + "%";
videoElement.removeEventListener("loadedmetadata", arguments.callee, false);
}, false);
videoSource = null;
videoElement = null;
}
function videoCopyAdd(videoElement)
{
var videoId = videoElement.id;
var videoCopy = videoElement.cloneNode(true);
videoElement.id = videoId + "-1";
videoCopy.id = videoId + "-2";
if (videoElement.nextSibling)
document.body.insertBefore(videoCopy, videoElement.nextSibling);
else
document.body.appendChild(videoCopy);
if (0.25 < videoElement.getBoundingClientRect().right / window.innerWidth && videoElement.getBoundingClientRect().left / window.innerWidth < 0.75)
{
videoCopy.style.left = 50 + parseFloat(videoElement.style.left) + "%";
videoElement.style.clip = "rect(0px, " + (0.25 * window.innerWidth - videoElement.getBoundingClientRect().left) + "px, " + videoElement.offsetHeight + "px, 0px)";
videoCopy.style.clip = "rect(0px, " + videoCopy.offsetWidth + "px, " + videoCopy.offsetHeight + "px, " + (0.75 * window.innerWidth - videoCopy.getBoundingClientRect().left) + "px)";
}
else if (videoElement.getBoundingClientRect().right / window.innerWidth > 1)
{
videoCopy.style.left = parseFloat(videoElement.style.left) - 100 + "%";
videoElement.style.clip = "rect(0px, " + (window.innerWidth - videoElement.getBoundingClientRect().left) + "px, " + videoElement.offsetHeight + "px, 0px)";
videoCopy.style.clip = "rect(0px, " + videoCopy.offsetWidth + "px, " + videoCopy.offsetHeight + "px, " + (0 - videoCopy.getBoundingClientRect().left) + "px)";
}
videoCopy.onclick = function()
{
var videoElement = this;
hiddenFunction(videoElement);;
}
videoCopy = null;
}
function videoCopyMove(videoElement)
{
var videoId = "video-" + videoElement.id.match(patternNumbers)[0];
var videoCopy = document.getElementById(videoId + "-2");
videoCopy.style.left = parseFloat(videoCopy.style.left) + 2 + "%";
if (0.25 < videoElement.getBoundingClientRect().right / window.innerWidth && videoElement.getBoundingClientRect().left / window.innerWidth < 0.75)
{
videoElement.style.clip = "rect(0px, " + (0.25 * window.innerWidth - videoElement.getBoundingClientRect().left) + "px, " + videoElement.offsetHeight + "px, 0px)";
videoCopy.style.clip = "rect(0px, " + videoCopy.offsetWidth + "px, " + videoCopy.offsetHeight + "px, " + (0.75 * window.innerWidth - videoCopy.getBoundingClientRect().left) + "px)";
}
else if (videoElement.getBoundingClientRect().right / window.innerWidth > 1)
{
videoElement.style.clip = "rect(0px, " + (window.innerWidth - videoElement.getBoundingClientRect().left) + "px, " + videoElement.offsetHeight + "px, 0px)";
videoCopy.style.clip = "rect(0px, " + videoCopy.offsetWidth + "px, " + videoCopy.offsetHeight + "px, " + (0 - videoCopy.getBoundingClientRect().left) + "px)";
}
if ((0.25 < videoElement.getBoundingClientRect().right / window.innerWidth && videoElement.getBoundingClientRect().left / window.innerWidth < 0.75 && videoCopy.getBoundingClientRect().left / window.innerWidth >= 0.75) || (videoElement.getBoundingClientRect().right / window.innerWidth > 1 && videoCopy.getBoundingClientRect().left / window.innerWidth >= 0))
{
videoElement.onclick = null;
for (var i = 0; i < videoElement.childNodes.length; i++)
{
videoElement.removeChild(videoElement.childNodes[i]);
}
videoElement.parentNode.removeChild(videoElement);
videoElement = null;
videoCopy.id = videoId;
document.getElementById(videoId).style.clip = "";
}
videoCopy = null;
}
function videoScroll()
{
var videoNonGridElements = document.getElementsByTagName("video");
for (var i = 0; i < videoNonGridElements.length; i++)
{
if (!videoNonGridElements[i].style.zIndex && videoNonGridElements[i].id.match(patternNumbers)[1] != 2)
{
var videoElement = videoNonGridElements[i];
var videoId = "video-" + videoElement.id.match(patternNumbers)[0];
videoElement.style.left = parseFloat(videoElement.style.left) + 2 + "%";
if (0.25 < videoElement.getBoundingClientRect().right / window.innerWidth && videoElement.getBoundingClientRect().left / window.innerWidth < 0.75 || videoElement.getBoundingClientRect().right / window.innerWidth > 1)
{
if (!document.getElementById(videoId + "-2"))
videoCopyAdd(videoElement);
else
videoCopyMove(videoElement);
}
videoElement = null;
}
}
videoNonGridElements = null;
videoAnimateScroll = setTimeout(function() { videoScroll(); }, 3000);
}
var videoAnimateScroll = setTimeout(function() { videoScroll(); }, 3000);
}, false);
I can give a demonstration but since this is a temporary project that won't be publicly available, I will not provide the link since it'll be broken in the future in case other people have similar problems. You can private message me for the link and I'll provide it. The JavaScript code here is exactly the same as the one in the demonstration so the solution can be archived here in the forums.
The memory leak appears when the video is chopped the first time, which is executed using the videoCopyAdd function. Now, of course, when the video is chopped, it creates another video element to be added in the DOM. That part I do understand the sudden increae in memory. However, when the chopped video has fully been shown, it removes one part of the chopped video from the DOM and the other video stays in the DOM. The problem here is that when the removal of the video from the DOM is done, the memory is not released. I suspect that it is either the videoCopyAdd function or the videoCopyMove function, or maybe both, that a variable is referencing a video node that has been removed from the DOM.
I have tested it on an old Chrome version (I believe version 31) and the memory leak is there. The newest Chrome version (36.0.1985.125) that I have tested it on has some problem with the CSS clip property. Nevertheless, the code still works, despite the appearance of the videos, and the memory leak still exists. Even so, Chrome mainly, not always, leaks the memory after a few minutes and is able to release the memory within the first few minutes. With FireFox, it's an entirely different story. The memory leaks indefinitely and does not release any memory at all, even on page refresh. I've used the timeline memory function in Chrome and I can confirm that the memory keeps increasing and the section where it shows a memory leak shows an increase amount of nodes, even if no new nodes have been added or nodes have been removed from the DOM. I tried isolating the problem with the heap snapshots but I haven't seen anything strange in the comparison of before and after a memory leak.
Any help would be greatly appreciated as I've been working on this issue for way too long now and I am very much out of ideas on what to do.