From e7e77d362c95f9baf3b3a511d791b0bc2d4e605c Mon Sep 17 00:00:00 2001 From: Jim Correia Date: Sat, 31 Aug 2019 22:10:20 -0700 Subject: [PATCH 1/2] =?UTF-8?q?Fix=20for=20bug=20#901=20=E2=80=94=C2=A0fir?= =?UTF-8?q?st=20web=20view=20load=20flashes=20white=20in=20dark=20mode?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The fix used for iOS (keep a queue of preloaded web views) isn't appropriate for macOS since the first view is used immediately. That approach would solve the flash of white when first searching, but not the flash of white when launching the application. Instead, use a modification of the original solution used for iOS: - wrap the web view in a box with an appropriate background color - hide the web view at creation - show the web view after* the first load This doesn't suffer the latency problem that the same solution on iOS had because the first load is always local, "No Selection" HTML. [*] Showing the view immediately after the first load still causes the flash to white. Waiting 0.05 seconds avoids this. That's a fairly terrible hack, but I don't have a better solution at present. --- .../Detail/DetailWebViewController.swift | 67 +++++++++++++++---- .../Contents.json | 38 +++++++++++ 2 files changed, 91 insertions(+), 14 deletions(-) create mode 100644 Mac/Resources/Assets.xcassets/webviewBackgroundColor.colorset/Contents.json diff --git a/Mac/MainWindow/Detail/DetailWebViewController.swift b/Mac/MainWindow/Detail/DetailWebViewController.swift index f0ac072dc..b4b92ad52 100644 --- a/Mac/MainWindow/Detail/DetailWebViewController.swift +++ b/Mac/MainWindow/Detail/DetailWebViewController.swift @@ -19,7 +19,7 @@ protocol DetailWebViewControllerDelegate: class { final class DetailWebViewController: NSViewController, WKUIDelegate { weak var delegate: DetailWebViewControllerDelegate? - var webview: DetailWebView! + var webView: DetailWebView! var state: DetailState = .noSelection { didSet { if state != oldValue { @@ -28,6 +28,7 @@ final class DetailWebViewController: NSViewController, WKUIDelegate { } } + private var waitingForFirstReload = false private let keyboardDelegate = DetailKeyboardDelegate() private struct MessageName { @@ -36,6 +37,16 @@ final class DetailWebViewController: NSViewController, WKUIDelegate { } override func loadView() { + // Wrap the webview in a box configured with the same background color that the web view uses + let box = NSBox(frame: .zero) + box.boxType = .custom + box.borderType = .noBorder + box.titlePosition = .noTitle + box.contentViewMargins = .zero + box.fillColor = NSColor(named: "webviewBackgroundColor")! + + view = box + let preferences = WKPreferences() preferences.minimumFontSize = 12.0 preferences.javaScriptCanOpenWindowsAutomatically = false @@ -51,18 +62,32 @@ final class DetailWebViewController: NSViewController, WKUIDelegate { userContentController.add(self, name: MessageName.mouseDidExit) configuration.userContentController = userContentController - webview = DetailWebView(frame: NSRect.zero, configuration: configuration) - webview.uiDelegate = self - webview.navigationDelegate = self - webview.keyboardDelegate = keyboardDelegate - webview.translatesAutoresizingMaskIntoConstraints = false + webView = DetailWebView(frame: NSRect.zero, configuration: configuration) + webView.uiDelegate = self + webView.navigationDelegate = self + webView.keyboardDelegate = keyboardDelegate + webView.translatesAutoresizingMaskIntoConstraints = false if let userAgent = UserAgent.fromInfoPlist() { - webview.customUserAgent = userAgent + webView.customUserAgent = userAgent } - view = webview - - self.reloadHTML() + box.addSubview(webView) + + let constraints = [ + webView.topAnchor.constraint(equalTo: view.topAnchor), + webView.bottomAnchor.constraint(equalTo: view.bottomAnchor), + webView.leadingAnchor.constraint(equalTo: view.leadingAnchor), + webView.trailingAnchor.constraint(equalTo: view.trailingAnchor), + ] + + NSLayoutConstraint.activate(constraints) + + // Hide the web view until the first reload (navigation) is complete (plus some delay) to avoid the awful white flash that happens on the initial display in dark mode. + // See bug #901. + webView.isHidden = true + waitingForFirstReload = true + + reloadHTML() } // MARK: Scrolling @@ -74,7 +99,7 @@ final class DetailWebViewController: NSViewController, WKUIDelegate { } override func scrollPageDown(_ sender: Any?) { - webview.scrollPageDown(sender) + webView.scrollPageDown(sender) } } @@ -107,6 +132,20 @@ extension DetailWebViewController: WKNavigationDelegate { decisionHandler(.allow) } + + public func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) { + // See note in viewDidLoad() + if waitingForFirstReload { + assert(webView.isHidden) + waitingForFirstReload = false + + // Waiting for the first navigation to complete isn't long enough to avoid the flash of white. + // A hard coded value is awful, but 5/100th of a second seems to be enough. + DispatchQueue.main.asyncAfter(deadline: .now() + 0.05) { + webView.isHidden = false + } + } + } } // MARK: - Private @@ -126,13 +165,13 @@ private extension DetailWebViewController { html = ArticleRenderer.articleHTML(article: article, style: style) } - webview.loadHTMLString(html, baseURL: nil) + webView.loadHTMLString(html, baseURL: nil) } func fetchScrollInfo(_ callback: @escaping (ScrollInfo?) -> Void) { let javascriptString = "var x = {contentHeight: document.body.scrollHeight, offsetY: document.body.scrollTop}; x" - webview.evaluateJavaScript(javascriptString) { (info, error) in + webView.evaluateJavaScript(javascriptString) { (info, error) in guard let info = info as? [String: Any] else { callback(nil) return @@ -142,7 +181,7 @@ private extension DetailWebViewController { return } - let scrollInfo = ScrollInfo(contentHeight: contentHeight, viewHeight: self.webview.frame.height, offsetY: offsetY) + let scrollInfo = ScrollInfo(contentHeight: contentHeight, viewHeight: self.webView.frame.height, offsetY: offsetY) callback(scrollInfo) } } diff --git a/Mac/Resources/Assets.xcassets/webviewBackgroundColor.colorset/Contents.json b/Mac/Resources/Assets.xcassets/webviewBackgroundColor.colorset/Contents.json new file mode 100644 index 000000000..c488d1243 --- /dev/null +++ b/Mac/Resources/Assets.xcassets/webviewBackgroundColor.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "info" : { + "version" : 1, + "author" : "xcode" + }, + "colors" : [ + { + "idiom" : "universal", + "color" : { + "color-space" : "srgb", + "components" : { + "red" : "1.000", + "alpha" : "1.000", + "blue" : "1.000", + "green" : "1.000" + } + } + }, + { + "idiom" : "universal", + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "color-space" : "display-p3", + "components" : { + "red" : "0.176", + "alpha" : "1.000", + "blue" : "0.176", + "green" : "0.176" + } + } + } + ] +} \ No newline at end of file From e31dec7c443e5728a7151a82d002592866a9fe99 Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Sun, 1 Sep 2019 10:49:35 -0500 Subject: [PATCH 2/2] Call completion handler when there are no feeds associated with the folder being removed. Issue #938 --- Frameworks/Account/Feedbin/FeedbinAccountDelegate.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/Frameworks/Account/Feedbin/FeedbinAccountDelegate.swift b/Frameworks/Account/Feedbin/FeedbinAccountDelegate.swift index 9131b703e..ac8aed461 100644 --- a/Frameworks/Account/Feedbin/FeedbinAccountDelegate.swift +++ b/Frameworks/Account/Feedbin/FeedbinAccountDelegate.swift @@ -276,6 +276,7 @@ final class FeedbinAccountDelegate: AccountDelegate { // Feedbin uses tags and if at least one feed isn't tagged, then the folder doesn't exist on their system guard folder.hasAtLeastOneFeed() else { account.removeFolder(folder) + completion(.success(())) return }