Fix for bug #901 — first web view load flashes white in dark mode

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.
This commit is contained in:
Jim Correia 2019-08-31 22:10:20 -07:00
parent fabea36a91
commit e7e77d362c
2 changed files with 91 additions and 14 deletions

View File

@ -19,7 +19,7 @@ protocol DetailWebViewControllerDelegate: class {
final class DetailWebViewController: NSViewController, WKUIDelegate { final class DetailWebViewController: NSViewController, WKUIDelegate {
weak var delegate: DetailWebViewControllerDelegate? weak var delegate: DetailWebViewControllerDelegate?
var webview: DetailWebView! var webView: DetailWebView!
var state: DetailState = .noSelection { var state: DetailState = .noSelection {
didSet { didSet {
if state != oldValue { if state != oldValue {
@ -28,6 +28,7 @@ final class DetailWebViewController: NSViewController, WKUIDelegate {
} }
} }
private var waitingForFirstReload = false
private let keyboardDelegate = DetailKeyboardDelegate() private let keyboardDelegate = DetailKeyboardDelegate()
private struct MessageName { private struct MessageName {
@ -36,6 +37,16 @@ final class DetailWebViewController: NSViewController, WKUIDelegate {
} }
override func loadView() { 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() let preferences = WKPreferences()
preferences.minimumFontSize = 12.0 preferences.minimumFontSize = 12.0
preferences.javaScriptCanOpenWindowsAutomatically = false preferences.javaScriptCanOpenWindowsAutomatically = false
@ -51,18 +62,32 @@ final class DetailWebViewController: NSViewController, WKUIDelegate {
userContentController.add(self, name: MessageName.mouseDidExit) userContentController.add(self, name: MessageName.mouseDidExit)
configuration.userContentController = userContentController configuration.userContentController = userContentController
webview = DetailWebView(frame: NSRect.zero, configuration: configuration) webView = DetailWebView(frame: NSRect.zero, configuration: configuration)
webview.uiDelegate = self webView.uiDelegate = self
webview.navigationDelegate = self webView.navigationDelegate = self
webview.keyboardDelegate = keyboardDelegate webView.keyboardDelegate = keyboardDelegate
webview.translatesAutoresizingMaskIntoConstraints = false webView.translatesAutoresizingMaskIntoConstraints = false
if let userAgent = UserAgent.fromInfoPlist() { if let userAgent = UserAgent.fromInfoPlist() {
webview.customUserAgent = userAgent webView.customUserAgent = userAgent
} }
view = webview box.addSubview(webView)
self.reloadHTML() 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 // MARK: Scrolling
@ -74,7 +99,7 @@ final class DetailWebViewController: NSViewController, WKUIDelegate {
} }
override func scrollPageDown(_ sender: Any?) { override func scrollPageDown(_ sender: Any?) {
webview.scrollPageDown(sender) webView.scrollPageDown(sender)
} }
} }
@ -107,6 +132,20 @@ extension DetailWebViewController: WKNavigationDelegate {
decisionHandler(.allow) 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 // MARK: - Private
@ -126,13 +165,13 @@ private extension DetailWebViewController {
html = ArticleRenderer.articleHTML(article: article, style: style) html = ArticleRenderer.articleHTML(article: article, style: style)
} }
webview.loadHTMLString(html, baseURL: nil) webView.loadHTMLString(html, baseURL: nil)
} }
func fetchScrollInfo(_ callback: @escaping (ScrollInfo?) -> Void) { func fetchScrollInfo(_ callback: @escaping (ScrollInfo?) -> Void) {
let javascriptString = "var x = {contentHeight: document.body.scrollHeight, offsetY: document.body.scrollTop}; x" 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 { guard let info = info as? [String: Any] else {
callback(nil) callback(nil)
return return
@ -142,7 +181,7 @@ private extension DetailWebViewController {
return 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) callback(scrollInfo)
} }
} }

View File

@ -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"
}
}
}
]
}