Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix GIF freezing issues. #755

Open
wants to merge 2 commits into
base: develop
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
105 changes: 56 additions & 49 deletions Awful.apk/src/main/assets/javascript/thread.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,11 @@ function containerInit() {
loadIgnoredPost(target, event);
return;
}
if (target.tagName === 'IMG' && target.hasAttribute('title') && target.src.endsWith('.gif')) {
if (target.tagName === 'IMG' && target.src.endsWith('.gif')) {
freezeGif(target);
return;
}
// TODO 2022/09/22: does this get called anymore? images get wrapped in <a> tags upstream, and gif freezing acts on the <a> tags.
if (target.tagName === 'CANVAS' && target.hasAttribute('title') && target.getAttribute('src').endsWith('.gif')) {
target.outerHTML = '<img src="' + target.getAttribute('src') + '" title="' + target.getAttribute('title') + '" />';
return;
Expand Down Expand Up @@ -186,34 +187,34 @@ function processPosts(scopeElement) {
highlightOwnQuotes(scopeElement);
}

// handle all GIFs that are not avatars
// handle all GIFs that are not avatars
if (listener.getPreference('disableGifs') === 'true') {
scopeElement.querySelectorAll('img[title][src$=".gif"]:not(.avatar)').forEach(prepareFreezeGif);
scopeElement.querySelectorAll('img[src$=".gif"]:not(.avatar)').forEach(prepareFreezeGif);
}

// this handles all avatar processing, meaning if the avatar is a GIF we need to handle freezing as well
scopeElement.querySelectorAll("img[title].avatar").forEach(function each(img) {
img.addEventListener('load', processSecondaryAvatar);
});
function processSecondaryAvatar() {
// when people want to use gangtags as avatars, etc., they often use a 1x1 image as their primary avatar.
// if this is the case, we change over to a "secondary" avatar, which is probably what's intended.
if (this.naturalWidth === 1 && this.naturalHeight === 1 && this.dataset.avatarSecondSrc && this.dataset.avatarSecondSrc.length) {
this.src = this.dataset.avatarSecondSrc;
}

if (listener.getPreference('disableGifs') === 'true' && this.src.slice(-4) === ".gif") {
prepareFreezeGif(this);
}
}
// this handles all avatar processing, meaning if the avatar is a GIF we need to handle freezing as well
scopeElement.querySelectorAll("img.avatar").forEach(function each(img) {
img.addEventListener('load', processSecondaryAvatar);
});
function processSecondaryAvatar() {
// when people want to use gangtags as avatars, etc., they often use a 1x1 image as their primary avatar.
// if this is the case, we change over to a "secondary" avatar, which is probably what's intended.
if (this.naturalWidth === 1 && this.naturalHeight === 1 && this.dataset.avatarSecondSrc && this.dataset.avatarSecondSrc.length) {
this.src = this.dataset.avatarSecondSrc;
}

if (listener.getPreference('disableGifs') === 'true' && this.src.slice(-4) === ".gif") {
prepareFreezeGif(this);
}
}
}

/**
* Eventhandler that pauses all videos that have been scrolled out of the viewport and starts all videos currently in the viewport
* @param {Element} scopeElement The element containing videos to pause
*/
function pauseVideosOutOfView(scopeElement) {
scopeElement = scopeElement || document;
scopeElement = scopeElement || document;
scopeElement.querySelectorAll('video').forEach(function eachVideo(video) {
if (isElementInViewport(video) && video.parentElement.parentElement.tagName !== 'BLOCKQUOTE' && video.firstElementChild.src.indexOf('webm') === -1) {
video.play();
Expand Down Expand Up @@ -309,16 +310,14 @@ function showInlineImage(url) {
}

/**
* Adds an empty Image Element to the Link if the link is not around a gif
* Adds an empty Image Element to the Link
* @param {Element} link Link Element
*/
function addEmptyImg(link) {
// basically treating anything not marked as a frozen gif as a text link
if (!link.classList.contains(FROZEN_GIF)) {
var image = document.createElement('img');
image.src = '';
link.appendChild(image);
} else {
var image = document.createElement('img');
image.src = '';
link.appendChild(image);
if (link.classList.contains(FROZEN_GIF)) {
link.classList.add(LOADING);
}
}
Expand All @@ -332,6 +331,9 @@ function showInlineImage(url) {
image.src = url;
image.style.height = 'auto';
image.style.width = 'auto';
if (link.classList.contains(FROZEN_GIF)) {
link.querySelector('canvas').replaceWith(image);
}
link.classList.remove(LOADING);
link.classList.remove(FROZEN_GIF);
}
Expand Down Expand Up @@ -371,6 +373,8 @@ function changeFontFace(font) {
* @param {Element} image Gif image that will be turned into a still canvas
*/
function freezeGif(image) {
var FROZEN_GIF = 'playGif';

var canvas = document.createElement('canvas');
var imageWidth = image.naturalWidth;
var imageHeight = image.naturalHeight;
Expand All @@ -382,20 +386,23 @@ function freezeGif(image) {
canvas.setAttribute(image.attributes[i].name, image.attributes[i].value);
}
image.parentNode.replaceChild(canvas, image);
if (canvas.parentNode.tagName === "A") {
canvas.parentNode.classList.add(FROZEN_GIF);
}
}

/**
* Monitors a gif to freeze it when loading's complete.
* @param {Element} image Gif image to monitor
*/
function prepareFreezeGif(image) {
if (!image.complete) {
image.addEventListener('load', function freezeLoadHandler() {
freezeGif(image);
});
} else {
freezeGif(image);
}
if (!image.complete) {
image.addEventListener('load', function freezeLoadHandler() {
freezeGif(image);
});
} else {
freezeGif(image);
}
}

/**
Expand Down Expand Up @@ -464,9 +471,9 @@ function handleQuoteLink(link, event) {
* @param {Element} info The HTMLElement of the postinfo
*/
function toggleInfo(info) {
var posterTitle = info.querySelector('.postinfo-title');
var posterRegDate = info.querySelector('.postinfo-regdate');
if (!posterTitle) { return; }
var posterTitle = info.querySelector('.postinfo-title');
var posterRegDate = info.querySelector('.postinfo-regdate');
if (!posterTitle) { return; }

if (posterTitle.classList.contains('extended')) {
if (info.querySelector('.avatar') !== null) {
Expand All @@ -475,15 +482,15 @@ function toggleInfo(info) {
info.querySelector('canvas').classList.add('avatar');
}
window.requestAnimationFrame(function shrinkAvatar() {
info.querySelector('.avatar').classList.remove('extended');
info.querySelector('.avatar').classList.remove('extended');
});
}
posterTitle.classList.remove('extended');
posterTitle.setAttribute('aria-hidden', 'true');
if (posterRegDate) {
posterRegDate.classList.remove('extended');
posterRegDate.setAttribute('aria-hidden', 'true');
}
posterRegDate.classList.remove('extended');
posterRegDate.setAttribute('aria-hidden', 'true');
}
} else {
if (info.querySelector('.avatar') !== null) {
if (info.querySelector('canvas') !== null) {
Expand All @@ -500,9 +507,9 @@ function toggleInfo(info) {
posterTitle.classList.add('extended');
posterTitle.setAttribute('aria-hidden', 'false');
if (posterRegDate) {
posterRegDate.classList.add('extended');
posterRegDate.setAttribute('aria-hidden', 'false');
}
posterRegDate.classList.add('extended');
posterRegDate.setAttribute('aria-hidden', 'false');
}
}
}

Expand Down Expand Up @@ -713,16 +720,16 @@ function handleTouchLeave() {
* Hides all instances of the given avatar on the page
*/
function hideAvatar(avatarUrl) {
document.querySelectorAll('[src="' + avatarUrl + '"]').forEach(function(avatarTag) {
avatarTag.classList.add('hide-avatar');
});
document.querySelectorAll('[src="' + avatarUrl + '"]').forEach(function(avatarTag) {
avatarTag.classList.add('hide-avatar');
});
}

/**
* Shows all instances of the given avatar on the page
*/
function showAvatar(avatarUrl) {
document.querySelectorAll('[src="' + avatarUrl + '"]').forEach(function(avatarTag) {
avatarTag.classList.remove('hide-avatar');
});
document.querySelectorAll('[src="' + avatarUrl + '"]').forEach(function(avatarTag) {
avatarTag.classList.remove('hide-avatar');
});
}