Flush are rebuild web view queue when suspending and restoring the app. Issue #1563

This commit is contained in:
Maurice Parker
2020-02-04 16:00:26 -08:00
parent 8ed9958631
commit dfd6be5fa6
6 changed files with 114 additions and 41 deletions

View File

@@ -75,6 +75,7 @@ class ArticleViewController: UIViewController {
NotificationCenter.default.addObserver(self, selector: #selector(unreadCountDidChange(_:)), name: .UnreadCountDidChange, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(statusesDidChange(_:)), name: .StatusesDidChange, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(contentSizeCategoryDidChange(_:)), name: UIContentSizeCategory.didChangeNotification, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(willEnterForeground(_:)), name: UIApplication.willEnterForegroundNotification, object: nil)
let fullScreenTapZone = UIView()
@@ -190,6 +191,15 @@ class ArticleViewController: UIViewController {
}
}
@objc func contentSizeCategoryDidChange(_ note: Notification) {
coordinator.webViewProvider.flushQueue()
coordinator.webViewProvider.replenishQueueIfNeeded()
if let controller = currentWebViewController {
controller.fullReload()
self.pageViewController.setViewControllers([controller], direction: .forward, animated: false, completion: nil)
}
}
@objc func willEnterForeground(_ note: Notification) {
// The toolbar will come back on you if you don't hide it again
if AppDefaults.articleFullscreenEnabled {

View File

@@ -89,7 +89,6 @@ class WebViewController: UIViewController {
NotificationCenter.default.addObserver(self, selector: #selector(webFeedIconDidBecomeAvailable(_:)), name: .WebFeedIconDidBecomeAvailable, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(avatarDidBecomeAvailable(_:)), name: .AvatarDidBecomeAvailable, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(faviconDidBecomeAvailable(_:)), name: .FaviconDidBecomeAvailable, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(contentSizeCategoryDidChange(_:)), name: UIContentSizeCategory.didChangeNotification, object: nil)
// Configure the tap zones
configureTopShowBarsView()
@@ -118,10 +117,6 @@ class WebViewController: UIViewController {
reloadArticleImage()
}
@objc func contentSizeCategoryDidChange(_ note: Notification) {
fullReload()
}
// MARK: Actions
@objc func showBars(_ sender: Any) {
@@ -354,21 +349,6 @@ extension WebViewController: WKScriptMessageHandler {
}
class WrapperScriptMessageHandler: NSObject, WKScriptMessageHandler {
// We need to wrap a message handler to prevent a circlular reference
private weak var handler: WKScriptMessageHandler?
init(_ handler: WKScriptMessageHandler) {
self.handler = handler
}
func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
handler?.userContentController(userContentController, didReceive: message)
}
}
// MARK: UIViewControllerTransitioningDelegate
extension WebViewController: UIViewControllerTransitioningDelegate {

View File

@@ -13,6 +13,10 @@ 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 {
private struct MessageName {
static let domContentLoaded = "domContentLoaded"
}
let articleIconSchemeHandler: ArticleIconSchemeHandler
private let minimumQueueDepth = 3
@@ -26,7 +30,31 @@ class WebViewProvider: NSObject, WKNavigationDelegate {
articleIconSchemeHandler = ArticleIconSchemeHandler(coordinator: coordinator)
super.init()
viewController.view.insertSubview(queue, at: 0)
replenishQueueIfNeeded()
NotificationCenter.default.addObserver(self, selector: #selector(didEnterBackground), name: UIApplication.didEnterBackgroundNotification, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(willEnterForeground), name: UIApplication.willEnterForegroundNotification, object: nil)
}
@objc func didEnterBackground() {
flushQueue()
}
@objc func willEnterForeground() {
replenishQueueIfNeeded()
}
func flushQueue() {
queue.subviews.forEach { $0.removeFromSuperview() }
waitingForFirstLoad = true
}
func replenishQueueIfNeeded() {
while queue.subviews.count < minimumQueueDepth {
let webView = WKWebView(frame: .zero, configuration: buildConfiguration())
enqueueWebView(webView)
}
}
func dequeueWebView(completion: @escaping (WKWebView) -> ()) {
@@ -42,11 +70,10 @@ class WebViewProvider: NSObject, WKNavigationDelegate {
return
}
webView.navigationDelegate = self
webView.configuration.userContentController.add(WrapperScriptMessageHandler(self), name: MessageName.domContentLoaded)
queue.insertSubview(webView, at: 0)
webView.loadFileURL(ArticleRenderer.page.url, allowingReadAccessTo: ArticleRenderer.page.baseURL)
}
// MARK: WKNavigationDelegate
@@ -63,30 +90,39 @@ class WebViewProvider: NSObject, WKNavigationDelegate {
}
}
// MARK: Private
}
private func replenishQueueIfNeeded() {
while queue.subviews.count < minimumQueueDepth {
let preferences = WKPreferences()
preferences.javaScriptCanOpenWindowsAutomatically = false
preferences.javaScriptEnabled = true
// MARK: WKScriptMessageHandler
let configuration = WKWebViewConfiguration()
configuration.preferences = preferences
configuration.setValue(true, forKey: "allowUniversalAccessFromFileURLs")
configuration.allowsInlineMediaPlayback = true
configuration.mediaTypesRequiringUserActionForPlayback = .video
configuration.setURLSchemeHandler(articleIconSchemeHandler, forURLScheme: ArticleRenderer.imageIconScheme)
let webView = WKWebView(frame: .zero, configuration: configuration)
enqueueWebView(webView)
extension WebViewProvider: WKScriptMessageHandler {
func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
switch message.name {
case MessageName.domContentLoaded:
if waitingForFirstLoad {
waitingForFirstLoad = false
if let completion = waitingCompletionHandler {
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
self.completeRequest(completion: completion)
self.waitingCompletionHandler = nil
}
}
}
default:
return
}
}
private func completeRequest(completion: @escaping (WKWebView) -> ()) {
}
// MARK: Private
private extension WebViewProvider {
func completeRequest(completion: @escaping (WKWebView) -> ()) {
if let webView = queue.subviews.last as? WKWebView {
webView.removeFromSuperview()
webView.navigationDelegate = nil
webView.configuration.userContentController.removeScriptMessageHandler(forName: MessageName.domContentLoaded)
replenishQueueIfNeeded()
completion(webView)
return
@@ -96,5 +132,19 @@ class WebViewProvider: NSObject, WKNavigationDelegate {
let webView = WKWebView(frame: .zero)
completion(webView)
}
func buildConfiguration() -> WKWebViewConfiguration {
let preferences = WKPreferences()
preferences.javaScriptCanOpenWindowsAutomatically = false
preferences.javaScriptEnabled = true
let configuration = WKWebViewConfiguration()
configuration.preferences = preferences
configuration.setValue(true, forKey: "allowUniversalAccessFromFileURLs")
configuration.allowsInlineMediaPlayback = true
configuration.mediaTypesRequiringUserActionForPlayback = .video
configuration.setURLSchemeHandler(articleIconSchemeHandler, forURLScheme: ArticleRenderer.imageIconScheme)
return configuration
}
}

View File

@@ -0,0 +1,25 @@
//
// WrapperScriptMessageHandler.swift
// NetNewsWire-iOS
//
// Created by Maurice Parker on 2/4/20.
// Copyright © 2020 Ranchero Software. All rights reserved.
//
import Foundation
import WebKit
class WrapperScriptMessageHandler: NSObject, WKScriptMessageHandler {
// We need to wrap a message handler to prevent a circlular reference
private weak var handler: WKScriptMessageHandler?
init(_ handler: WKScriptMessageHandler) {
self.handler = handler
}
func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
handler?.userContentController(userContentController, didReceive: message)
}
}

View File

@@ -156,3 +156,7 @@ function stopMediaPlayback() {
element.pause();
});
}
window.addEventListener('DOMContentLoaded', (event) => {
window.webkit.messageHandlers.domContentLoaded.postMessage("");
});