Files
Attractor/SBrowser/Browser View/BrowserViewController.swift

285 lines
11 KiB
Swift
Raw Normal View History

//
// BrowserViewController.swift
// SBrowser
//
// Created by James Magahern on 7/21/20.
//
import UIKit
import UniformTypeIdentifiers
class BrowserViewController: UIViewController, WKNavigationDelegate,
2020-07-29 19:24:05 -07:00
UITextFieldDelegate, ScriptPolicyViewControllerDelegate,
UIPopoverPresentationControllerDelegate, TabDelegate, TabPickerViewControllerDelegate
{
let browserView = BrowserView()
var tab: Tab { didSet { didChangeTab(tab) } }
var webView: WKWebView { tab.webView }
private let tabController = TabController()
2020-07-24 19:26:35 -07:00
private let toolbarController = ToolbarViewController()
private var policyManager: ResourcePolicyManager { tabController.policyManager }
2020-07-24 19:26:35 -07:00
override var canBecomeFirstResponder: Bool { true }
2020-07-29 14:16:25 -07:00
private var titleObservation: NSKeyValueObservation?
2020-07-28 11:31:30 -07:00
private var loadingObservation: NSKeyValueObservation?
private var backButtonObservation: NSKeyValueObservation?
private var forwardButtonObservation: NSKeyValueObservation?
2020-07-28 11:31:30 -07:00
2020-07-29 14:16:25 -07:00
override var preferredStatusBarStyle: UIStatusBarStyle { .lightContent }
2020-07-24 19:26:35 -07:00
init() {
self.tab = tabController.tabs.first!
super.init(nibName: nil, bundle: nil)
addChild(toolbarController)
didChangeTab(tab)
}
required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") }
2020-07-24 19:26:35 -07:00
override func loadView() {
browserView.toolbarView = toolbarController.toolbarView
// Refresh button
toolbarController.urlBar.refreshButton.addAction(UIAction(handler: { [webView] action in
if webView.isLoading {
webView.stopLoading()
2020-07-28 11:37:10 -07:00
} else {
webView.reload()
}
}), for: .touchUpInside)
// Back button
toolbarController.backButton.addAction(UIAction(handler: { [webView] _ in
webView.goBack()
}), for: .touchUpInside)
// Forward button
toolbarController.forwardButton.addAction(UIAction(handler: { [webView] _ in
webView.goForward()
}), for: .touchUpInside)
// Share button
toolbarController.shareButton.addAction(UIAction(handler: { [unowned self, webView, toolbarController] _ in
if let url = webView.url {
let itemProvider = NSItemProvider(item: url as NSURL, typeIdentifier: UTType.url.identifier)
let config = UIActivityItemsConfiguration(itemProviders: [ itemProvider ])
config.metadataProvider = { metadataKey in
switch metadataKey {
case .title: return webView.title
case .messageBody: return webView.title
default: return nil
}
}
config.previewProvider = { index, intent, suggestedSize in
NSItemProvider(item: self.tab.favicon, typeIdentifier: UTType.image.identifier)
}
let activityController = UIActivityViewController(activityItemsConfiguration: config)
activityController.popoverPresentationController?.sourceView = toolbarController.shareButton
self.present(activityController, animated: true, completion: nil)
2020-07-28 11:37:10 -07:00
}
2020-07-24 19:26:35 -07:00
}), for: .touchUpInside)
// Script button
toolbarController.scriptControllerIconView.addAction(UIAction(handler: { [unowned self] action in
let hostOrigin = self.webView.url?.host ?? ""
let loadedScripts = self.tab.allowedScriptOrigins.union(self.tab.blockedScriptOrigins)
let scriptViewController = ScriptPolicyViewController(policyManager: self.policyManager,
hostOrigin: hostOrigin,
loadedScripts: loadedScripts,
scriptsAllowedForTab: self.tab.javaScriptEnabled)
2020-07-24 19:26:35 -07:00
scriptViewController.delegate = self
let navController = UINavigationController(rootViewController: scriptViewController)
2020-07-29 18:34:46 -07:00
navController.modalPresentationStyle = .popover
navController.popoverPresentationController?.sourceView = self.toolbarController.scriptControllerIconView
2020-07-29 19:24:05 -07:00
navController.popoverPresentationController?.delegate = self
2020-07-29 18:34:46 -07:00
2020-07-24 19:26:35 -07:00
self.present(navController, animated: true, completion: nil)
}), for: .touchUpInside)
2020-07-29 18:17:22 -07:00
// Dark mode button
toolbarController.darkModeButton.addAction(UIAction(handler: { [tab, toolbarController] _ in
tab.bridge.darkModeEnabled = !tab.bridge.darkModeEnabled
toolbarController.darkModeEnabled = tab.bridge.darkModeEnabled
}), for: .touchUpInside)
// Tabs button
toolbarController.windowButton.addAction(UIAction(handler: { [unowned self] _ in
let tabPickerController = TabPickerViewController(tabController: self.tabController)
tabPickerController.delegate = self
tabPickerController.selectedTab = self.tab
let navController = UINavigationController(rootViewController: tabPickerController)
navController.modalPresentationStyle = .popover
navController.popoverPresentationController?.sourceView = self.toolbarController.windowButton
navController.popoverPresentationController?.delegate = self
self.present(navController, animated: true, completion: nil)
2020-07-29 18:17:22 -07:00
}), for: .touchUpInside)
2020-07-24 19:26:35 -07:00
// TextField delegate
toolbarController.urlBar.textField.delegate = self
self.view = browserView
}
private func updateLoadProgress(forWebView webView: WKWebView) {
if webView.estimatedProgress == 1.0 {
toolbarController.urlBar.loadProgress = .complete
} else {
toolbarController.urlBar.loadProgress = .loading(progress: webView.estimatedProgress)
}
}
private func updateTitleAndURL(forWebView webView: WKWebView) {
browserView.titlebarView.titleLabelView.text = webView.title
if let urlString = webView.url?.absoluteString {
toolbarController.urlBar.textField.text = urlString
}
}
private func didChangeTab(_ tab: Tab) {
tab.delegate = self
let webView = tab.webView
webView.allowsBackForwardNavigationGestures = true
webView.navigationDelegate = self
// Change webView
browserView.webView = webView
2020-07-28 11:31:30 -07:00
// Load progress
updateLoadProgress(forWebView: webView)
loadingObservation = webView.observe(\.estimatedProgress) { [unowned self] (webView, observedChange) in
self.updateLoadProgress(forWebView: webView)
2020-07-28 11:31:30 -07:00
}
2020-07-29 14:16:25 -07:00
// Title observer
updateTitleAndURL(forWebView: webView)
titleObservation = webView.observe(\.title, changeHandler: { [unowned self] (webView, observedChange) in
self.updateTitleAndURL(forWebView: webView)
})
// Back/forward observer
toolbarController.backButton.isEnabled = webView.canGoBack
backButtonObservation = webView.observe(\.canGoBack, changeHandler: { [toolbarController] (webView, observedChange) in
toolbarController.backButton.isEnabled = webView.canGoBack
})
toolbarController.forwardButton.isEnabled = webView.canGoForward
forwardButtonObservation = webView.observe(\.canGoForward, changeHandler: { [toolbarController] (webView, observedChange) in
toolbarController.forwardButton.isEnabled = webView.canGoForward
2020-07-29 14:16:25 -07:00
})
// Script blocker button
updateScriptBlockerButton()
// Dark mode status
toolbarController.darkModeEnabled = tab.bridge.darkModeEnabled
}
2020-07-24 19:26:35 -07:00
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
becomeFirstResponder()
}
private func updateScriptBlockerButton() {
toolbarController.scriptControllerIconView.setBlockedScriptsNumber(tab.blockedScriptOrigins.count)
}
2020-07-29 19:24:05 -07:00
// MARK: UIPopoverPresentationControllerDelegate
func adaptivePresentationStyle(for controller: UIPresentationController, traitCollection: UITraitCollection) -> UIModalPresentationStyle {
// Forces popovers to present on iPhone
return .none
}
2020-07-24 19:26:35 -07:00
// MARK: Navigation Delegate
func webView(_ webView: WKWebView, didStartProvisionalNavigation navigation: WKNavigation!) {
// Reset tracking this
tab.blockedScriptOrigins.removeAll()
2020-07-24 19:26:35 -07:00
updateTitleAndURL(forWebView: webView)
// Start requesting favicon
if let url = webView.url {
tab.updateFaviconForURL(url)
2020-07-24 19:26:35 -07:00
}
}
2020-07-28 11:31:30 -07:00
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
toolbarController.urlBar.loadProgress = .complete
}
func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, preferences: WKWebpagePreferences, decisionHandler: @escaping (WKNavigationActionPolicy, WKWebpagePreferences) -> Void)
{
var allowJavaScript = tab.javaScriptEnabled
if !allowJavaScript, let host = navigationAction.request.url?.host {
// Check origin policy
allowJavaScript = policyManager.allowedOriginsForScriptResources().contains(host)
}
preferences.allowsContentJavaScript = allowJavaScript
decisionHandler(.allow, preferences)
}
2020-07-24 19:26:35 -07:00
// MARK: UITextField Delegate
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
if let text = textField.text, let url = URL(string: text) {
if url.scheme == nil {
let urlString = "https://\(text)"
if let url = URL(string: urlString) {
tab.beginLoadingURL(url)
2020-07-24 19:26:35 -07:00
}
}
}
textField.resignFirstResponder()
return false
}
// MARK: Tab Delegate
func didBlockScriptOrigin(_ origin: String, forTab: Tab) {
updateScriptBlockerButton()
}
// MARK: Tab Picker Delegate
func tabPicker(_ picker: TabPickerViewController, didSelectTab tab: Tab) {
self.tab = tab
picker.dismiss(animated: true, completion: nil)
}
func tabPicker(_ picker: TabPickerViewController, willCloseTab tab: Tab) {
// If closed tab is current tab, pick another one.
if tab == self.tab {
if let nextTab = tabController.tabs.last(where: { $0 != tab }) {
self.tab = nextTab
}
picker.dismiss(animated: true, completion: nil)
}
}
2020-07-24 19:26:35 -07:00
// MARK: Script Policy View Controller Delegate
func didChangeScriptPolicy() {
tab.bridge.policyDataSourceDidChange()
webView.reload()
2020-07-24 19:26:35 -07:00
}
func setScriptsEnabledForTab(_ enabled: Bool) {
tab.javaScriptEnabled = enabled
}
}