Make the article icon code specify the desired article so that it can't pull the wrong one by mistake. Issue #1707

This commit is contained in:
Maurice Parker
2020-01-27 12:58:32 -07:00
parent 470f2159e8
commit 3a99e6430f
9 changed files with 112 additions and 29 deletions

View File

@@ -58,8 +58,6 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
super.init()
appDelegate = self
// Force lazy initialization of the web view provider so that it can warm up the queue of prepared web views
let _ = WebViewProvider.shared
AccountManager.shared = AccountManager()
NotificationCenter.default.addObserver(self, selector: #selector(unreadCountDidChange(_:)), name: .UnreadCountDidChange, object: nil)

View File

@@ -0,0 +1,58 @@
//
// ArticleIconSchemeHandler.swift
// NetNewsWire-iOS
//
// Created by Maurice Parker on 1/27/20.
// Copyright © 2020 Ranchero Software. All rights reserved.
//
import Foundation
import WebKit
import Articles
class ArticleIconSchemeHandler: NSObject, WKURLSchemeHandler {
let coordinator: SceneCoordinator
init(coordinator: SceneCoordinator) {
self.coordinator = coordinator
}
func webView(_ webView: WKWebView, start urlSchemeTask: WKURLSchemeTask) {
guard let url = urlSchemeTask.request.url else {
urlSchemeTask.didFailWithError(URLError(.fileDoesNotExist))
return
}
let articleID = url.absoluteString.stripping(prefix: "\(ArticleRenderer.imageIconScheme)://")
guard let iconImage = coordinator.articleFor(articleID)?.iconImage() else {
urlSchemeTask.didFailWithError(URLError(.fileDoesNotExist))
return
}
let iconView = IconView(frame: CGRect(x: 0, y: 0, width: 48, height: 48))
iconView.iconImage = iconImage
let renderedImage = iconView.asImage()
guard let data = renderedImage.dataRepresentation() else {
urlSchemeTask.didFailWithError(URLError(.fileDoesNotExist))
return
}
let headerFields = ["Cache-Control": "no-cache"]
if let response = HTTPURLResponse(url: url, statusCode: 200, httpVersion: nil, headerFields: headerFields) {
urlSchemeTask.didReceive(response)
urlSchemeTask.didReceive(data)
urlSchemeTask.didFinish()
}
}
func webView(_ webView: WKWebView, stop urlSchemeTask: WKURLSchemeTask) {
urlSchemeTask.didFailWithError(URLError(.unknown))
}
}

View File

@@ -83,7 +83,7 @@ class WebViewController: UIViewController {
webView.configuration.userContentController.removeScriptMessageHandler(forName: MessageName.imageWasClicked)
webView.configuration.userContentController.removeScriptMessageHandler(forName: MessageName.imageWasShown)
webView.removeFromSuperview()
WebViewProvider.shared.enqueueWebView(webView)
coordinator.webViewProvider.enqueueWebView(webView)
webView = nil
}
}
@@ -96,7 +96,7 @@ class WebViewController: UIViewController {
NotificationCenter.default.addObserver(self, selector: #selector(faviconDidBecomeAvailable(_:)), name: .FaviconDidBecomeAvailable, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(contentSizeCategoryDidChange(_:)), name: UIContentSizeCategory.didChangeNotification, object: nil)
WebViewProvider.shared.dequeueWebView() { webView in
coordinator.webViewProvider.dequeueWebView() { webView in
// Add the webview
self.webView = webView
@@ -486,7 +486,6 @@ private extension WebViewController {
restoreWindowScrollY = 0
WebViewProvider.shared.articleIconSchemeHandler.currentArticle = article
webView.scrollView.setZoomScale(1.0, animated: false)
webView.evaluateJavaScript(render)

View File

@@ -13,9 +13,7 @@ import WebKit
/// Keep a queue of WebViews where we've already done a trivial load so that by the time we need them in the UI, they're past the flash-to-shite part of their lifecycle.
class WebViewProvider: NSObject, WKNavigationDelegate {
static let shared = WebViewProvider()
let articleIconSchemeHandler = ArticleIconSchemeHandler()
let articleIconSchemeHandler: ArticleIconSchemeHandler
private let minimumQueueDepth = 3
private let maximumQueueDepth = 6
@@ -24,6 +22,12 @@ class WebViewProvider: NSObject, WKNavigationDelegate {
private var waitingForFirstLoad = true
private var waitingCompletionHandler: ((WKWebView) -> ())?
init(coordinator: SceneCoordinator) {
articleIconSchemeHandler = ArticleIconSchemeHandler(coordinator: coordinator)
super.init()
replenishQueueIfNeeded()
}
func dequeueWebView(completion: @escaping (WKWebView) -> ()) {
if waitingForFirstLoad {
waitingCompletionHandler = completion
@@ -58,11 +62,6 @@ class WebViewProvider: NSObject, WKNavigationDelegate {
// MARK: Private
private override init() {
super.init()
replenishQueueIfNeeded()
}
private func replenishQueueIfNeeded() {
while queue.count < minimumQueueDepth {
let preferences = WKPreferences()

View File

@@ -30,6 +30,8 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, UnreadCountProvider {
return rootSplitViewController.undoManager
}
lazy var webViewProvider = WebViewProvider(coordinator: self)
private var panelMode: PanelMode = .unset
private var activityManager = ActivityManager()
@@ -255,9 +257,19 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, UnreadCountProvider {
private(set) var articles = ArticleArray() {
didSet {
timelineMiddleIndexPath = nil
articleDictionaryNeedsUpdate = true
}
}
private var articleDictionaryNeedsUpdate = true
private var _idToArticleDictionary = [String: Article]()
private var idToAticleDictionary: [String: Article] {
if articleDictionaryNeedsUpdate {
rebuildArticleDictionaries()
}
return _idToArticleDictionary
}
private var currentArticleRow: Int? {
guard let article = currentArticle else { return nil }
return articles.firstIndex(of: article)
@@ -572,6 +584,10 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, UnreadCountProvider {
return shadowTable[section]
}
func articleFor(_ articleID: String) -> Article? {
return idToAticleDictionary[articleID]
}
func cappedIndexPath(_ indexPath: IndexPath) -> IndexPath {
guard indexPath.section < shadowTable.count && indexPath.row < shadowTable[indexPath.section].count else {
return IndexPath(row: shadowTable[shadowTable.count - 1].count - 1, section: shadowTable.count - 1)
@@ -1222,6 +1238,17 @@ private extension SceneCoordinator {
unreadCount = count
}
func rebuildArticleDictionaries() {
var idDictionary = [String: Article]()
articles.forEach { article in
idDictionary[article.articleID] = article
}
_idToArticleDictionary = idDictionary
articleDictionaryNeedsUpdate = false
}
func rebuildBackingStores(initialLoad: Bool = false, updateExpandedNodes: (() -> Void)? = nil) {
if !animatingChanges && !BatchUpdate.shared.isPerforming {
treeController.rebuild()