diff --git a/iOS/Article/ArticleViewController.swift b/iOS/Article/ArticleViewController.swift index b74cc65c6..36d43bf72 100644 --- a/iOS/Article/ArticleViewController.swift +++ b/iOS/Article/ArticleViewController.swift @@ -85,6 +85,9 @@ class ArticleViewController: UIViewController { deinit { if webView != nil { + webView?.evaluateJavaScript("cancelImageLoad();") + webView.configuration.userContentController.removeScriptMessageHandler(forName: MessageName.imageWasClicked) + webView.configuration.userContentController.removeScriptMessageHandler(forName: MessageName.imageWasShown) webView.removeFromSuperview() ArticleViewControllerWebViewProvider.shared.enqueueWebView(webView) webView = nil @@ -109,8 +112,6 @@ class ArticleViewController: UIViewController { webView.navigationDelegate = self webView.uiDelegate = self - webView.configuration.userContentController.removeScriptMessageHandler(forName: MessageName.imageWasClicked) - webView.configuration.userContentController.removeScriptMessageHandler(forName: MessageName.imageWasShown) webView.configuration.userContentController.add(WrapperScriptMessageHandler(self), name: MessageName.imageWasClicked) webView.configuration.userContentController.add(WrapperScriptMessageHandler(self), name: MessageName.imageWasShown) diff --git a/iOS/Resources/main_ios.js b/iOS/Resources/main_ios.js index 2af2e5a81..7a87e51c1 100644 --- a/iOS/Resources/main_ios.js +++ b/iOS/Resources/main_ios.js @@ -1,23 +1,37 @@ -var imageIsLoading = false; +var controller = new AbortController() + +// Cancel any pending image loads (there might be none) and reset the controller +function cancelImageLoad() { + controller.abort(); + controller = new AbortController(); +} // Used to pop a resizable image view async function imageWasClicked(img) { - img.classList.add("nnwClicked"); + cancelImageLoad(); + showNetworkLoading(img); try { - showNetworkLoading(img); - const response = await fetch(img.src); + + const signal = controller.signal; + const response = await fetch(img.src, { signal: signal }); if (!response.ok) { throw new Error('Network response was not ok.'); } const imgBlob = await response.blob(); + if (signal.aborted) { + throw new Error('Network response was aborted.'); + } + hideNetworkLoading(img); - + var reader = new FileReader(); reader.readAsDataURL(imgBlob); reader.onloadend = function() { + img.classList.add("nnwClicked"); + const rect = img.getBoundingClientRect(); var message = { x: rect.x, @@ -29,8 +43,9 @@ async function imageWasClicked(img) { var jsonMessage = JSON.stringify(message); window.webkit.messageHandlers.imageWasClicked.postMessage(jsonMessage); - + } + } catch (error) { hideNetworkLoading(img); console.log('There has been a problem with your fetch operation: ', error.message); @@ -39,7 +54,6 @@ async function imageWasClicked(img) { } function showNetworkLoading(img) { - imageIsLoading = true; var wrapper = document.createElement("div"); wrapper.classList.add("activityIndicatorWrap"); @@ -64,8 +78,6 @@ function hideNetworkLoading(img) { var wrapperParent = wrapper.parentNode; wrapperParent.insertBefore(img, wrapper); wrapperParent.removeChild(wrapper); - - imageIsLoading = false; } // Used to animate the transition to a fullscreen image @@ -85,7 +97,7 @@ function showClickedImage() { // Add the click listener for images function imageClicks() { window.onclick = function(event) { - if (event.target.matches('img') && !imageIsLoading) { + if (event.target.matches('img')) { imageWasClicked(event.target); } } @@ -101,8 +113,9 @@ function inlineVideos() { } function postRenderProcessing() { - imageClicks() - inlineVideos() + cancelImageLoad(); + imageClicks(); + inlineVideos(); } const activityIndicator = "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+PHN2ZyB4bWxuczpzdmc9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB2ZXJzaW9uPSIxLjAiIHdpZHRoPSI2NHB4IiBoZWlnaHQ9IjY0cHgiIHZpZXdCb3g9IjAgMCAxMjggMTI4IiB4bWw6c3BhY2U9InByZXNlcnZlIj48Zz48cGF0aCBkPSJNNTkuNiAwaDh2NDBoLThWMHoiIGZpbGw9IiMwMDAwMDAiLz48cGF0aCBkPSJNNTkuNiAwaDh2NDBoLThWMHoiIGZpbGw9IiNjY2NjY2MiIHRyYW5zZm9ybT0icm90YXRlKDMwIDY0IDY0KSIvPjxwYXRoIGQ9Ik01OS42IDBoOHY0MGgtOFYweiIgZmlsbD0iI2NjY2NjYyIgdHJhbnNmb3JtPSJyb3RhdGUoNjAgNjQgNjQpIi8+PHBhdGggZD0iTTU5LjYgMGg4djQwaC04VjB6IiBmaWxsPSIjY2NjY2NjIiB0cmFuc2Zvcm09InJvdGF0ZSg5MCA2NCA2NCkiLz48cGF0aCBkPSJNNTkuNiAwaDh2NDBoLThWMHoiIGZpbGw9IiNjY2NjY2MiIHRyYW5zZm9ybT0icm90YXRlKDEyMCA2NCA2NCkiLz48cGF0aCBkPSJNNTkuNiAwaDh2NDBoLThWMHoiIGZpbGw9IiNiMmIyYjIiIHRyYW5zZm9ybT0icm90YXRlKDE1MCA2NCA2NCkiLz48cGF0aCBkPSJNNTkuNiAwaDh2NDBoLThWMHoiIGZpbGw9IiM5OTk5OTkiIHRyYW5zZm9ybT0icm90YXRlKDE4MCA2NCA2NCkiLz48cGF0aCBkPSJNNTkuNiAwaDh2NDBoLThWMHoiIGZpbGw9IiM3ZjdmN2YiIHRyYW5zZm9ybT0icm90YXRlKDIxMCA2NCA2NCkiLz48cGF0aCBkPSJNNTkuNiAwaDh2NDBoLThWMHoiIGZpbGw9IiM2NjY2NjYiIHRyYW5zZm9ybT0icm90YXRlKDI0MCA2NCA2NCkiLz48cGF0aCBkPSJNNTkuNiAwaDh2NDBoLThWMHoiIGZpbGw9IiM0YzRjNGMiIHRyYW5zZm9ybT0icm90YXRlKDI3MCA2NCA2NCkiLz48cGF0aCBkPSJNNTkuNiAwaDh2NDBoLThWMHoiIGZpbGw9IiMzMzMzMzMiIHRyYW5zZm9ybT0icm90YXRlKDMwMCA2NCA2NCkiLz48cGF0aCBkPSJNNTkuNiAwaDh2NDBoLThWMHoiIGZpbGw9IiMxOTE5MTkiIHRyYW5zZm9ybT0icm90YXRlKDMzMCA2NCA2NCkiLz48YW5pbWF0ZVRyYW5zZm9ybSBhdHRyaWJ1dGVOYW1lPSJ0cmFuc2Zvcm0iIHR5cGU9InJvdGF0ZSIgdmFsdWVzPSIwIDY0IDY0OzMwIDY0IDY0OzYwIDY0IDY0OzkwIDY0IDY0OzEyMCA2NCA2NDsxNTAgNjQgNjQ7MTgwIDY0IDY0OzIxMCA2NCA2NDsyNDAgNjQgNjQ7MjcwIDY0IDY0OzMwMCA2NCA2NDszMzAgNjQgNjQiIGNhbGNNb2RlPSJkaXNjcmV0ZSIgZHVyPSIxMDgwbXMiIHJlcGVhdENvdW50PSJpbmRlZmluaXRlIj48L2FuaW1hdGVUcmFuc2Zvcm0+PC9nPjwvc3ZnPg==";