mirror of
https://github.com/keymanapp/keyman.git
synced 2026-08-06 00:45:32 +00:00
Fixes #7915. On iPadOS 16, it seems that WKWebView defaults to lying about its user agent to servers. We can switch this behaviour by using .mobile for the preferredContentMode. We should also update the website to work around this, so that users always get the right downloads for their device, but the website currently uses server-side determination of user agent for this particular aspect of the keyboard search, so that's a much more significant refactor, to make it client-side, with greater risk and much more testing required.
123 lines
3.8 KiB
Swift
123 lines
3.8 KiB
Swift
//
|
|
// PackageWebViewController.swift
|
|
// KeymanEngine
|
|
//
|
|
// Created by Joshua Horton on 23/9/20.
|
|
// Copyright © 2020 SIL International. All rights reserved.
|
|
//
|
|
|
|
import Foundation
|
|
import UIKit
|
|
import WebKit
|
|
|
|
class PackageWebViewController: UIViewController, WKNavigationDelegate {
|
|
private var webView: WKWebView!
|
|
private let package: KeymanPackage
|
|
private let homePage: KeymanPackagePage
|
|
|
|
public init?(for package: KeymanPackage, page: KeymanPackagePage) {
|
|
self.package = package
|
|
self.homePage = page
|
|
|
|
switch(page) {
|
|
case .readme:
|
|
// We always provide a default readme text for packages, even if it's raw text.
|
|
break
|
|
default:
|
|
if package.pageURL(for: page) == nil {
|
|
return nil
|
|
}
|
|
}
|
|
|
|
super.init(nibName: nil, bundle: nil)
|
|
|
|
_ = view
|
|
}
|
|
|
|
required init?(coder: NSCoder) {
|
|
fatalError("init(coder:) has not been implemented")
|
|
}
|
|
|
|
override func loadView() {
|
|
let config = WKWebViewConfiguration()
|
|
let prefs = WKPreferences()
|
|
prefs.javaScriptEnabled = true
|
|
|
|
if #available(iOS 13.0, *) {
|
|
/**
|
|
In iPadOS 16 and above WKWebView defaults to lying about its user-agent,
|
|
telling the web server that it is a mac. We can avoid this with .mobile:
|
|
*/
|
|
let pref = WKWebpagePreferences.init()
|
|
pref.preferredContentMode = .mobile
|
|
config.defaultWebpagePreferences = pref
|
|
}
|
|
|
|
// Inject a meta viewport tag into the head of the file if it doesn't exist
|
|
let metaViewportInjection = """
|
|
if(!document.querySelectorAll('meta[name=viewport]').length) {
|
|
let meta=document.createElement('meta');
|
|
meta.name='viewport';
|
|
meta.content='width=device-width, initial-scale=1';
|
|
document.head.appendChild(meta);
|
|
}
|
|
"""
|
|
|
|
let injection = WKUserScript(source: metaViewportInjection, injectionTime: .atDocumentStart, forMainFrameOnly: true)
|
|
let controller = WKUserContentController()
|
|
controller.addUserScript(injection)
|
|
config.preferences = prefs
|
|
config.userContentController = controller
|
|
|
|
webView = WKWebView(frame: CGRect.zero, configuration: config)
|
|
webView!.isOpaque = false
|
|
webView!.backgroundColor = UIColor.white
|
|
webView!.navigationDelegate = self
|
|
webView!.scrollView.isScrollEnabled = true
|
|
|
|
view = webView
|
|
reloadHomePage()
|
|
}
|
|
|
|
private func reloadHomePage() {
|
|
switch(self.homePage) {
|
|
case .readme: // Cannot if-check against _not_ matching an enum in an if. Thanks, Swift.
|
|
if package.pageURL(for: .readme) == nil {
|
|
webView.loadHTMLString(package.infoHtml(), baseURL: package.sourceFolder)
|
|
} else {
|
|
fallthrough
|
|
}
|
|
default:
|
|
let url = package.pageURL(for: self.homePage)! // we already know it exists
|
|
webView.loadFileURL(url, allowingReadAccessTo: package.sourceFolder)
|
|
}
|
|
}
|
|
|
|
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
|
|
guard let _ = webView.url else {
|
|
return
|
|
}
|
|
}
|
|
|
|
// Used to intercept links that should be handled externally.
|
|
public func webView(_ webView: WKWebView,
|
|
decidePolicyFor navigationAction: WKNavigationAction,
|
|
decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {
|
|
if let link = navigationAction.request.url {
|
|
if link.path.hasPrefix(self.package.sourceFolder.path) {
|
|
// Links from within the package will show up as 'external' in the condition below!
|
|
// So, we check against package-internal links first.
|
|
decisionHandler(.allow)
|
|
return
|
|
} else if UniversalLinks.isExternalLink(link) {
|
|
decisionHandler(.cancel)
|
|
|
|
// Kick this link out to an external Safari process.
|
|
UniversalLinks.externalLinkLauncher?(link)
|
|
return
|
|
}
|
|
}
|
|
|
|
decisionHandler(.allow)
|
|
}
|
|
}
|