2020-07-22 19:29:38 -07:00
|
|
|
//
|
|
|
|
|
// BrowserViewController.swift
|
|
|
|
|
// SBrowser
|
|
|
|
|
//
|
|
|
|
|
// Created by James Magahern on 7/21/20.
|
|
|
|
|
//
|
|
|
|
|
|
2020-10-28 17:57:34 -07:00
|
|
|
import Combine
|
2021-05-06 16:19:39 -07:00
|
|
|
import MessageUI
|
2020-07-22 19:29:38 -07:00
|
|
|
import UIKit
|
2020-07-31 14:08:10 -07:00
|
|
|
import UniformTypeIdentifiers
|
2020-07-22 19:29:38 -07:00
|
|
|
|
2021-02-17 18:28:18 -08:00
|
|
|
class BrowserViewController: UIViewController
|
2020-07-22 19:29:38 -07:00
|
|
|
{
|
|
|
|
|
let browserView = BrowserView()
|
2020-07-30 23:54:20 -07:00
|
|
|
var tab: Tab { didSet { didChangeTab(tab) } }
|
|
|
|
|
var webView: WKWebView { tab.webView }
|
2020-07-22 19:29:38 -07:00
|
|
|
|
2021-02-17 18:28:18 -08:00
|
|
|
internal let tabController = TabController()
|
|
|
|
|
internal let tabBarViewController: TabBarViewController
|
|
|
|
|
internal let toolbarController = ToolbarViewController()
|
|
|
|
|
internal let findOnPageController = FindOnPageViewController()
|
2020-07-30 23:54:20 -07:00
|
|
|
|
2021-02-17 18:28:18 -08:00
|
|
|
internal let autocompleteViewController = AutocompleteViewController()
|
|
|
|
|
internal var policyManager: ResourcePolicyManager { tabController.policyManager }
|
2020-07-30 23:54:20 -07:00
|
|
|
|
2020-07-24 19:26:35 -07:00
|
|
|
override var canBecomeFirstResponder: Bool { true }
|
2020-07-22 19:29:38 -07:00
|
|
|
|
2020-07-28 11:31:30 -07:00
|
|
|
private var loadingObservation: NSKeyValueObservation?
|
2020-07-31 14:08:10 -07:00
|
|
|
private var backButtonObservation: NSKeyValueObservation?
|
|
|
|
|
private var forwardButtonObservation: NSKeyValueObservation?
|
2021-06-14 18:09:33 -07:00
|
|
|
private var hasSecureContentObservation: NSKeyValueObservation?
|
2020-10-28 17:57:34 -07:00
|
|
|
private var activeTabObservation: AnyCancellable?
|
2020-07-28 11:31:30 -07:00
|
|
|
|
2022-02-21 16:01:01 -08:00
|
|
|
internal var shiftKeyHeld: Bool = false
|
2021-02-17 18:28:18 -08:00
|
|
|
internal var commandKeyHeld: Bool = false
|
|
|
|
|
internal var windowButtonHeld: Bool {
|
2021-02-15 22:56:20 -08:00
|
|
|
get { toolbarController.newTabButton.isTracking }
|
|
|
|
|
set { toolbarController.newTabButton.cancelTracking(with: nil) }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static let longPressWindowButtonToMakeNewTab: Bool = false
|
2020-11-10 16:51:34 -06:00
|
|
|
|
2021-03-08 23:38:01 -08:00
|
|
|
internal var darkModeEnabled: Bool {
|
2021-02-15 22:47:02 -08:00
|
|
|
get { tab.bridge.darkModeEnabled }
|
|
|
|
|
set {
|
|
|
|
|
tab.bridge.darkModeEnabled = newValue
|
|
|
|
|
toolbarController.darkModeEnabled = newValue
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2021-05-25 15:38:37 -07:00
|
|
|
internal enum PreferredEmailSharingRecipient: String, CaseIterable {
|
|
|
|
|
case readLater = "Read Later"
|
|
|
|
|
case bookmark = "Bookmark"
|
|
|
|
|
case other = "Other…"
|
|
|
|
|
}
|
|
|
|
|
|
2020-07-29 14:16:25 -07:00
|
|
|
override var preferredStatusBarStyle: UIStatusBarStyle { .lightContent }
|
|
|
|
|
|
2021-03-09 00:14:48 -08:00
|
|
|
internal var changingFocusToAutocompleteController = false
|
|
|
|
|
|
2020-07-24 19:26:35 -07:00
|
|
|
init() {
|
2020-07-30 23:54:20 -07:00
|
|
|
self.tab = tabController.tabs.first!
|
2020-10-28 17:57:34 -07:00
|
|
|
self.tabBarViewController = TabBarViewController(tabController: tabController)
|
2020-07-22 19:29:38 -07:00
|
|
|
super.init(nibName: nil, bundle: nil)
|
2020-07-30 23:54:20 -07:00
|
|
|
|
2022-02-21 16:01:01 -08:00
|
|
|
self.tabController.controllerDelegate = self
|
|
|
|
|
|
2020-07-30 23:54:20 -07:00
|
|
|
addChild(toolbarController)
|
2020-09-30 18:06:47 -07:00
|
|
|
addChild(findOnPageController)
|
2020-10-28 17:57:34 -07:00
|
|
|
addChild(tabBarViewController)
|
2020-09-30 18:06:47 -07:00
|
|
|
|
2020-07-30 23:54:20 -07:00
|
|
|
didChangeTab(tab)
|
2020-07-22 19:29:38 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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
|
2020-10-28 17:57:34 -07:00
|
|
|
browserView.tabBarView = tabBarViewController.tabBarView
|
2020-07-24 19:26:35 -07:00
|
|
|
|
2022-03-28 16:52:03 -07:00
|
|
|
if FindOnPageViewController.isEnabled() {
|
|
|
|
|
browserView.findOnPageView = findOnPageController.findOnPageView
|
|
|
|
|
|
|
|
|
|
// Find on page dismiss
|
|
|
|
|
findOnPageController.findOnPageView.doneButton.addAction(UIAction(handler: { [unowned self] _ in
|
|
|
|
|
browserView.setFindOnPageVisible(false, animated: true)
|
|
|
|
|
}), for: .touchUpInside)
|
|
|
|
|
}
|
|
|
|
|
|
2020-07-24 19:26:35 -07:00
|
|
|
// Refresh button
|
2020-07-31 14:39:18 -07:00
|
|
|
toolbarController.urlBar.refreshButton.addAction(UIAction(handler: { [unowned self] action in
|
|
|
|
|
if self.webView.isLoading {
|
|
|
|
|
self.webView.stopLoading()
|
2020-07-28 11:37:10 -07:00
|
|
|
} else {
|
2020-07-31 14:39:18 -07:00
|
|
|
self.webView.reload()
|
2020-07-31 14:08:10 -07:00
|
|
|
}
|
|
|
|
|
}), for: .touchUpInside)
|
|
|
|
|
|
|
|
|
|
// Back button
|
2020-07-31 14:39:18 -07:00
|
|
|
toolbarController.backButton.addAction(UIAction(handler: { [unowned self] _ in
|
|
|
|
|
self.webView.goBack()
|
2020-07-31 14:08:10 -07:00
|
|
|
}), for: .touchUpInside)
|
|
|
|
|
|
|
|
|
|
// Forward button
|
2020-07-31 14:39:18 -07:00
|
|
|
toolbarController.forwardButton.addAction(UIAction(handler: { [unowned self] _ in
|
|
|
|
|
self.webView.goForward()
|
2020-07-31 14:08:10 -07:00
|
|
|
}), for: .touchUpInside)
|
|
|
|
|
|
|
|
|
|
// Share button
|
2021-05-06 16:19:39 -07:00
|
|
|
toolbarController.shareButton.addAction(UIAction(handler: { [unowned self] _ in
|
|
|
|
|
showShareSheetForCurrentURL(fromViewController: nil)
|
2020-07-24 19:26:35 -07:00
|
|
|
}), for: .touchUpInside)
|
|
|
|
|
|
|
|
|
|
// Script button
|
2020-07-31 14:08:10 -07:00
|
|
|
toolbarController.scriptControllerIconView.addAction(UIAction(handler: { [unowned self] action in
|
2020-07-30 23:54:20 -07:00
|
|
|
let hostOrigin = self.webView.url?.host ?? ""
|
|
|
|
|
let loadedScripts = self.tab.allowedScriptOrigins.union(self.tab.blockedScriptOrigins)
|
2020-07-29 18:58:31 -07:00
|
|
|
let scriptViewController = ScriptPolicyViewController(policyManager: self.policyManager,
|
|
|
|
|
hostOrigin: hostOrigin,
|
|
|
|
|
loadedScripts: loadedScripts,
|
2020-07-30 23:54:20 -07:00
|
|
|
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
|
2020-07-31 14:47:18 -07:00
|
|
|
toolbarController.darkModeButton.addAction(UIAction(handler: { [unowned self] _ in
|
2021-02-15 22:47:02 -08:00
|
|
|
self.darkModeEnabled = !self.darkModeEnabled
|
2020-07-30 23:54:20 -07:00
|
|
|
}), for: .touchUpInside)
|
|
|
|
|
|
|
|
|
|
// Tabs button
|
2020-07-31 14:08:10 -07:00
|
|
|
toolbarController.windowButton.addAction(UIAction(handler: { [unowned self] _ in
|
2020-07-30 23:54:20 -07:00
|
|
|
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-31 16:36:10 -07:00
|
|
|
let newTabAction = UIAction { [unowned self] action in
|
|
|
|
|
if let gestureRecognizer = action.sender as? UILongPressGestureRecognizer {
|
|
|
|
|
if gestureRecognizer.state != .began { return }
|
|
|
|
|
}
|
|
|
|
|
|
2021-05-06 16:42:04 -07:00
|
|
|
self.createNewTab(withURL: nil)
|
2020-07-31 16:36:10 -07:00
|
|
|
}
|
|
|
|
|
|
2021-02-15 22:56:20 -08:00
|
|
|
if Self.longPressWindowButtonToMakeNewTab {
|
|
|
|
|
// Long press window button to make new tab?
|
|
|
|
|
|
|
|
|
|
let gestureRecognizer = UILongPressGestureRecognizer(action: newTabAction)
|
|
|
|
|
toolbarController.windowButton.addGestureRecognizer(gestureRecognizer)
|
|
|
|
|
}
|
2020-07-31 16:36:10 -07:00
|
|
|
|
|
|
|
|
// New tab button
|
|
|
|
|
toolbarController.newTabButton.addAction(newTabAction, for: .touchUpInside)
|
|
|
|
|
|
2020-07-31 18:29:44 -07:00
|
|
|
// Error button
|
|
|
|
|
toolbarController.urlBar.errorButton.addAction(UIAction(handler: { [unowned self] _ in
|
2021-05-06 16:42:04 -07:00
|
|
|
let alert = UIAlertController(title: "Error", message: self.tab.loadError?.localizedDescription, preferredStyle: .actionSheet)
|
2020-07-31 18:33:04 -07:00
|
|
|
alert.popoverPresentationController?.sourceView = self.toolbarController.urlBar.errorButton
|
2020-07-31 18:29:44 -07:00
|
|
|
|
|
|
|
|
alert.addAction(UIAlertAction(title: "Reload", style: .destructive, handler: { _ in
|
|
|
|
|
self.webView.reload()
|
|
|
|
|
alert.dismiss(animated: true, completion: nil)
|
|
|
|
|
}))
|
|
|
|
|
|
2022-02-21 16:01:01 -08:00
|
|
|
alert.addAction(UIAlertAction(title: "OK", style: .default, handler: { [unowned self] _ in
|
2020-07-31 18:29:44 -07:00
|
|
|
alert.dismiss(animated: true, completion: nil)
|
2021-01-07 12:47:25 -08:00
|
|
|
|
|
|
|
|
// Clears out the error state
|
2022-02-21 16:01:01 -08:00
|
|
|
self.toolbarController.urlBar.loadProgress = .complete
|
2020-07-31 18:29:44 -07:00
|
|
|
}))
|
|
|
|
|
|
|
|
|
|
self.present(alert, animated: true, completion: nil)
|
|
|
|
|
}), for: .touchUpInside)
|
|
|
|
|
|
2020-09-21 17:56:22 -07:00
|
|
|
// Cancel button: hide autocomplete if applicable
|
|
|
|
|
toolbarController.toolbarView.cancelButton.addAction(UIAction(handler: { [unowned self] _ in
|
|
|
|
|
self.autocompleteViewController.view.isHidden = true
|
|
|
|
|
}), for: .touchUpInside)
|
|
|
|
|
|
2020-07-24 19:26:35 -07:00
|
|
|
// TextField delegate
|
2021-03-09 00:14:48 -08:00
|
|
|
toolbarController.urlBar.delegate = self
|
2020-07-24 19:26:35 -07:00
|
|
|
toolbarController.urlBar.textField.delegate = self
|
2020-07-22 19:29:38 -07:00
|
|
|
|
2020-09-21 17:56:22 -07:00
|
|
|
// Autocomplete controller
|
|
|
|
|
autocompleteViewController.delegate = self
|
|
|
|
|
autocompleteViewController.view.isHidden = true
|
|
|
|
|
|
2020-09-22 15:37:13 -07:00
|
|
|
// Font size adjust
|
|
|
|
|
toolbarController.urlBar.documentButton.addAction(UIAction(handler: { [unowned self] _ in
|
2021-02-15 22:47:02 -08:00
|
|
|
let documentControls = DocumentControlViewController(darkModeEnabled: tab.bridge.darkModeEnabled)
|
2020-09-22 15:37:13 -07:00
|
|
|
documentControls.modalPresentationStyle = .popover
|
|
|
|
|
documentControls.popoverPresentationController?.permittedArrowDirections = [ .down, .up ]
|
|
|
|
|
documentControls.popoverPresentationController?.sourceView = toolbarController.urlBar.documentButton
|
|
|
|
|
documentControls.popoverPresentationController?.delegate = self
|
|
|
|
|
|
|
|
|
|
let numberFormatter = NumberFormatter()
|
|
|
|
|
numberFormatter.numberStyle = .percent
|
|
|
|
|
|
|
|
|
|
let label = documentControls.fontSizeAdjustView.labelView
|
|
|
|
|
label.text = numberFormatter.string(for: tab.webView._viewScale)
|
|
|
|
|
|
2021-02-11 12:26:13 -08:00
|
|
|
// Font size adjust
|
2020-11-10 16:41:19 -06:00
|
|
|
documentControls.fontSizeAdjustView.decreaseSizeButton.addAction(UIAction(handler: { [unowned self] sender in
|
|
|
|
|
self.decreaseSize(sender)
|
2020-09-22 15:37:13 -07:00
|
|
|
label.text = numberFormatter.string(for: tab.webView._viewScale)
|
|
|
|
|
}), for: .touchUpInside)
|
|
|
|
|
|
2020-11-10 16:41:19 -06:00
|
|
|
documentControls.fontSizeAdjustView.increaseSizeButton.addAction(UIAction(handler: { [unowned self] sender in
|
|
|
|
|
self.increaseSize(sender)
|
2020-09-22 15:37:13 -07:00
|
|
|
label.text = numberFormatter.string(for: tab.webView._viewScale)
|
|
|
|
|
}), for: .touchUpInside)
|
|
|
|
|
|
2021-02-11 12:26:13 -08:00
|
|
|
// Find on page
|
2020-09-30 18:06:47 -07:00
|
|
|
documentControls.findOnPageControlView.addAction(UIAction(handler: { [unowned self] _ in
|
|
|
|
|
documentControls.dismiss(animated: true, completion: nil)
|
2022-03-28 16:52:03 -07:00
|
|
|
|
|
|
|
|
if FindOnPageViewController.isEnabled() {
|
|
|
|
|
browserView.setFindOnPageVisible(true, animated: true)
|
|
|
|
|
} else if #available(iOS 16.0, *) {
|
|
|
|
|
browserView.webView?._findInteraction.presentFindNavigatorShowingReplace(false)
|
|
|
|
|
}
|
2020-09-30 18:06:47 -07:00
|
|
|
}), for: .touchUpInside)
|
|
|
|
|
|
2021-02-11 12:26:13 -08:00
|
|
|
// Navigation controls
|
|
|
|
|
documentControls.navigationControlView.backButton.isEnabled = webView.canGoBack
|
|
|
|
|
documentControls.navigationControlView.backButton.addAction(UIAction() { [unowned self] _ in
|
|
|
|
|
webView.goBack()
|
|
|
|
|
}, for: .touchUpInside)
|
|
|
|
|
|
2021-03-30 15:39:24 -07:00
|
|
|
documentControls.observations.append(webView.observe(\.canGoBack, changeHandler: { (webView, _) in
|
2021-02-11 12:26:13 -08:00
|
|
|
documentControls.navigationControlView.backButton.isEnabled = webView.canGoBack
|
|
|
|
|
}))
|
|
|
|
|
|
|
|
|
|
documentControls.navigationControlView.forwardButton.isEnabled = webView.canGoForward
|
|
|
|
|
documentControls.navigationControlView.forwardButton.addAction(UIAction() { [unowned self] _ in
|
|
|
|
|
webView.goForward()
|
|
|
|
|
}, for: .touchUpInside)
|
|
|
|
|
|
2021-03-30 15:39:24 -07:00
|
|
|
documentControls.observations.append(webView.observe(\.canGoForward, changeHandler: { (webView, _) in
|
2021-02-11 12:26:13 -08:00
|
|
|
documentControls.navigationControlView.forwardButton.isEnabled = webView.canGoForward
|
|
|
|
|
}))
|
|
|
|
|
|
2021-02-15 22:34:05 -08:00
|
|
|
// Reader mode
|
|
|
|
|
documentControls.readabilityView.addAction(UIAction { [unowned self] _ in
|
|
|
|
|
tab.bridge.parseDocumentForReaderMode { string in
|
|
|
|
|
DispatchQueue.main.async {
|
|
|
|
|
documentControls.dismiss(animated: true, completion: nil)
|
|
|
|
|
|
2022-02-21 16:01:01 -08:00
|
|
|
let readableViewController = ReaderViewController(readableHTMLString: string, baseURL: self.tab.bridge.webView.url)
|
|
|
|
|
readableViewController.title = self.tab.bridge.webView.title
|
|
|
|
|
readableViewController.darkModeEnabled = self.tab.bridge.darkModeEnabled
|
2021-02-15 22:34:05 -08:00
|
|
|
readableViewController.delegate = self
|
|
|
|
|
|
|
|
|
|
let navigationController = UINavigationController(rootViewController: readableViewController)
|
2022-02-21 16:01:01 -08:00
|
|
|
self.present(navigationController, animated: true, completion: nil)
|
2021-02-15 22:34:05 -08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}, for: .touchUpInside)
|
|
|
|
|
|
2021-02-23 17:08:35 -08:00
|
|
|
// Archive
|
|
|
|
|
documentControls.archiveView.addAction(UIAction { [unowned self] _ in
|
|
|
|
|
guard let currentURL = webView.url else { return }
|
|
|
|
|
guard let archiveURL = URL(string: "https://archive.today/\(currentURL.absoluteString)") else { return }
|
|
|
|
|
|
2021-05-25 15:42:42 -07:00
|
|
|
// Open in new tab:
|
|
|
|
|
createNewTab(withURL: archiveURL)
|
2021-02-23 17:08:35 -08:00
|
|
|
documentControls.dismiss(animated: true, completion: nil)
|
|
|
|
|
}, for: .touchUpInside)
|
|
|
|
|
|
2021-02-15 22:47:02 -08:00
|
|
|
// Dark mode
|
|
|
|
|
documentControls.darkModeView.addAction(UIAction { [unowned self] _ in
|
|
|
|
|
self.darkModeEnabled = !self.darkModeEnabled
|
|
|
|
|
documentControls.dismiss(animated: true, completion: nil)
|
|
|
|
|
}, for: .touchUpInside)
|
|
|
|
|
|
2021-05-06 16:19:39 -07:00
|
|
|
// Email
|
|
|
|
|
documentControls.emailView.addAction(UIAction { [unowned self] _ in
|
2021-05-25 15:38:37 -07:00
|
|
|
queryPreferredEmailSharingRecipientForCurrentURL(fromViewController: documentControls)
|
2021-05-06 16:19:39 -07:00
|
|
|
}, for: .touchUpInside)
|
|
|
|
|
|
2021-03-03 16:17:54 -08:00
|
|
|
// Settings
|
|
|
|
|
documentControls.settingsView.addAction(UIAction { [unowned self] _ in
|
|
|
|
|
documentControls.dismiss(animated: false, completion: nil)
|
|
|
|
|
showSettingsWindow()
|
|
|
|
|
}, for: .touchUpInside)
|
2021-07-13 18:11:47 -07:00
|
|
|
|
|
|
|
|
// Share
|
|
|
|
|
documentControls.sharingView.addAction(UIAction { [unowned self] _ in
|
|
|
|
|
showShareSheetForCurrentURL(fromViewController: documentControls)
|
|
|
|
|
}, for: .touchUpInside)
|
2021-03-03 16:17:54 -08:00
|
|
|
|
2020-09-22 15:37:13 -07:00
|
|
|
present(documentControls, animated: true, completion: nil)
|
|
|
|
|
}), for: .touchUpInside)
|
|
|
|
|
|
2020-10-28 17:57:34 -07:00
|
|
|
// Tab controller
|
|
|
|
|
activeTabObservation = tabController.$activeTabIndex
|
|
|
|
|
.receive(on: RunLoop.main)
|
|
|
|
|
.sink(receiveValue: { [unowned self] (activeTab: Int) in
|
2021-02-11 19:20:06 -08:00
|
|
|
if activeTab < tabController.tabs.count {
|
|
|
|
|
let tab = tabController.tabs[activeTab]
|
|
|
|
|
if self.tab != tab {
|
|
|
|
|
self.tab = tab
|
|
|
|
|
}
|
2020-10-28 17:57:34 -07:00
|
|
|
}
|
2021-02-15 18:13:38 -08:00
|
|
|
|
|
|
|
|
// Show tab bar view?
|
2022-02-21 16:01:01 -08:00
|
|
|
self.updateTabBarVisibility()
|
2020-10-28 17:57:34 -07:00
|
|
|
})
|
|
|
|
|
|
2020-07-30 23:54:20 -07:00
|
|
|
self.view = browserView
|
|
|
|
|
}
|
|
|
|
|
|
2022-02-21 16:01:01 -08:00
|
|
|
internal func updateTabBarVisibility() {
|
|
|
|
|
browserView.tabBarViewVisible = tabController.tabs.count > 1
|
|
|
|
|
}
|
|
|
|
|
|
2021-05-06 16:19:39 -07:00
|
|
|
internal func showShareSheetForCurrentURL(fromViewController: UIViewController?) {
|
|
|
|
|
guard let url = self.webView.url else { return }
|
|
|
|
|
|
|
|
|
|
let shareableURL = ShareableURL(
|
|
|
|
|
url: url,
|
|
|
|
|
title: webView.title ?? url.absoluteString,
|
|
|
|
|
favicon: tab.favicon
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
let activityController = UIActivityViewController(activityItems: [ shareableURL ], applicationActivities: nil)
|
|
|
|
|
activityController.popoverPresentationController?.sourceView = toolbarController.shareButton
|
|
|
|
|
|
|
|
|
|
if let fromViewController = fromViewController {
|
|
|
|
|
fromViewController.dismiss(animated: false, completion: nil)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
self.present(activityController, animated: true, completion: nil)
|
|
|
|
|
}
|
|
|
|
|
|
2021-05-25 15:38:37 -07:00
|
|
|
internal func queryPreferredEmailSharingRecipientForCurrentURL(fromViewController: UIViewController?) {
|
|
|
|
|
if let fromViewController = fromViewController {
|
|
|
|
|
fromViewController.dismiss(animated: false, completion: nil)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let actionSheet = UIAlertController(title: "Select Preferred Recipient", message: nil, preferredStyle: .actionSheet)
|
|
|
|
|
for preferredRecipient in PreferredEmailSharingRecipient.allCases {
|
|
|
|
|
actionSheet.addAction(UIAlertAction(title: preferredRecipient.rawValue, style: .default, handler: { [unowned self] action in
|
|
|
|
|
actionSheet.dismiss(animated: true, completion: { [unowned self] in
|
|
|
|
|
composeEmailForCurrentURL(preferredRecipientSelection: preferredRecipient)
|
|
|
|
|
})
|
|
|
|
|
}))
|
|
|
|
|
}
|
|
|
|
|
|
2021-05-25 15:40:58 -07:00
|
|
|
actionSheet.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: { _ in
|
|
|
|
|
actionSheet.dismiss(animated: true, completion: nil)
|
|
|
|
|
}))
|
|
|
|
|
|
2021-06-14 16:47:57 -07:00
|
|
|
actionSheet.popoverPresentationController?.sourceView = toolbarController.urlBar.documentButton
|
|
|
|
|
|
2021-05-25 15:38:37 -07:00
|
|
|
present(actionSheet, animated: true, completion: nil)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
internal func composeEmailForCurrentURL(preferredRecipientSelection: PreferredEmailSharingRecipient) {
|
2021-05-06 16:19:39 -07:00
|
|
|
guard let url = self.webView.url else { return }
|
|
|
|
|
|
|
|
|
|
let composeController = MFMailComposeViewController()
|
|
|
|
|
composeController.setSubject(webView.title ?? url.absoluteString)
|
|
|
|
|
composeController.setMessageBody(url.absoluteString, isHTML: false)
|
|
|
|
|
composeController.mailComposeDelegate = self
|
|
|
|
|
|
2021-05-25 15:38:37 -07:00
|
|
|
let preferredRecipient: String? = { preferredRecipientSelection in
|
|
|
|
|
switch preferredRecipientSelection {
|
|
|
|
|
case .readLater:
|
|
|
|
|
return "read@buzzert.net"
|
|
|
|
|
case .bookmark:
|
|
|
|
|
return "bookmarks@buzzert.net"
|
|
|
|
|
case .other:
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
}(preferredRecipientSelection)
|
|
|
|
|
|
|
|
|
|
if let preferredRecipient = preferredRecipient {
|
|
|
|
|
composeController.setToRecipients([ preferredRecipient ])
|
2021-05-06 16:19:39 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
present(composeController, animated: true, completion: nil)
|
|
|
|
|
}
|
|
|
|
|
|
2021-03-03 16:17:54 -08:00
|
|
|
internal func showSettingsWindow() {
|
|
|
|
|
#if targetEnvironment(macCatalyst)
|
|
|
|
|
let userActivity = NSUserActivity(activityType: SessionActivityType.SettingsWindow.rawValue)
|
|
|
|
|
UIApplication.shared.requestSceneSessionActivation(nil, userActivity: userActivity, options: .none, errorHandler: nil)
|
|
|
|
|
#else
|
2021-06-29 18:09:42 -07:00
|
|
|
let settingsVC = SettingsViewController(windowScene: view.window!.windowScene!)
|
|
|
|
|
let wrapperNC = UINavigationController(rootViewController: settingsVC)
|
|
|
|
|
present(wrapperNC, animated: true, completion: nil)
|
2021-03-03 16:17:54 -08:00
|
|
|
#endif
|
|
|
|
|
}
|
|
|
|
|
|
2021-02-17 18:28:18 -08:00
|
|
|
internal func updateLoadProgress(forWebView webView: WKWebView) {
|
2021-05-06 16:42:04 -07:00
|
|
|
if let loadError = tab.loadError {
|
2020-07-31 18:29:44 -07:00
|
|
|
toolbarController.urlBar.loadProgress = .error(error: loadError)
|
|
|
|
|
} else if webView.estimatedProgress == 1.0 {
|
2020-07-30 23:54:20 -07:00
|
|
|
toolbarController.urlBar.loadProgress = .complete
|
2021-07-20 19:17:04 -07:00
|
|
|
} else if webView.isLoading {
|
2020-07-30 23:54:20 -07:00
|
|
|
toolbarController.urlBar.loadProgress = .loading(progress: webView.estimatedProgress)
|
2021-07-20 19:17:04 -07:00
|
|
|
} else {
|
|
|
|
|
toolbarController.urlBar.loadProgress = .idle
|
2020-07-30 23:54:20 -07:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2021-02-17 18:28:18 -08:00
|
|
|
internal func updateTitleAndURL(forWebView webView: WKWebView) {
|
2021-02-15 19:21:48 -08:00
|
|
|
if webView == browserView.webView {
|
|
|
|
|
browserView.titlebarView.setTitle(webView.title ?? "")
|
|
|
|
|
|
|
|
|
|
if let urlString = webView.url?.absoluteString {
|
|
|
|
|
toolbarController.urlBar.textField.text = urlString
|
|
|
|
|
} else {
|
|
|
|
|
toolbarController.urlBar.textField.text = ""
|
|
|
|
|
}
|
2020-07-30 23:54:20 -07:00
|
|
|
}
|
2020-10-28 17:57:34 -07:00
|
|
|
|
|
|
|
|
// Figure out which tab this corresponds to
|
|
|
|
|
let tab = tabController.tabs.first { $0.webView == webView }
|
|
|
|
|
if let tab = tab, let tabIndex = tabController.tabs.firstIndex(of: tab) {
|
|
|
|
|
tabBarViewController.tabBarView.reloadTab(atIndex: tabIndex)
|
|
|
|
|
}
|
2020-07-30 23:54:20 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private func didChangeTab(_ tab: Tab) {
|
2020-10-28 17:57:34 -07:00
|
|
|
if let activeIndex = tabController.tabs.firstIndex(of: tab) {
|
|
|
|
|
tabController.activeTabIndex = activeIndex
|
|
|
|
|
}
|
|
|
|
|
|
2020-07-30 23:54:20 -07:00
|
|
|
tab.delegate = self
|
|
|
|
|
|
|
|
|
|
let webView = tab.webView
|
|
|
|
|
webView.allowsBackForwardNavigationGestures = true
|
|
|
|
|
webView.navigationDelegate = self
|
2020-09-21 15:39:41 -07:00
|
|
|
webView.uiDelegate = self
|
2020-07-30 23:54:20 -07:00
|
|
|
|
|
|
|
|
// Change webView
|
|
|
|
|
browserView.webView = webView
|
2020-09-30 18:06:47 -07:00
|
|
|
findOnPageController.webView = webView
|
2020-07-30 23:54:20 -07:00
|
|
|
|
2020-09-21 17:56:22 -07:00
|
|
|
// Autocomplete view
|
2022-02-18 19:12:50 -08:00
|
|
|
browserView.autocompleteView = autocompleteViewController.collectionView
|
2020-09-21 17:56:22 -07:00
|
|
|
|
2021-02-11 17:54:22 -08:00
|
|
|
// Color theme
|
|
|
|
|
browserView.titlebarView.setColorTheme(tab.colorTheme)
|
|
|
|
|
|
2020-07-28 11:31:30 -07:00
|
|
|
// Load progress
|
2020-07-30 23:54:20 -07:00
|
|
|
updateLoadProgress(forWebView: webView)
|
2020-07-31 14:08:10 -07:00
|
|
|
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
|
2020-07-30 23:54:20 -07:00
|
|
|
updateTitleAndURL(forWebView: webView)
|
2020-07-31 14:08:10 -07:00
|
|
|
|
|
|
|
|
// Back/forward observer
|
|
|
|
|
toolbarController.backButton.isEnabled = webView.canGoBack
|
2021-06-14 18:09:33 -07:00
|
|
|
backButtonObservation = webView.observe(\.canGoBack, changeHandler: { [unowned self] (webView, observedChange) in
|
2020-07-31 14:08:10 -07:00
|
|
|
toolbarController.backButton.isEnabled = webView.canGoBack
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
toolbarController.forwardButton.isEnabled = webView.canGoForward
|
2021-06-14 18:09:33 -07:00
|
|
|
forwardButtonObservation = webView.observe(\.canGoForward, changeHandler: { [unowned self] (webView, observedChange) in
|
2020-07-31 14:08:10 -07:00
|
|
|
toolbarController.forwardButton.isEnabled = webView.canGoForward
|
2020-07-29 14:16:25 -07:00
|
|
|
})
|
|
|
|
|
|
2021-06-14 18:09:33 -07:00
|
|
|
// Secure content
|
|
|
|
|
browserView.titlebarView.showsSecurityIndicator = webView.hasOnlySecureContent
|
|
|
|
|
hasSecureContentObservation = webView.observe(\.hasOnlySecureContent, changeHandler: { [unowned self] (webView, observedChange) in
|
|
|
|
|
browserView.titlebarView.showsSecurityIndicator = webView.hasOnlySecureContent
|
|
|
|
|
})
|
|
|
|
|
|
2020-07-30 23:54:20 -07:00
|
|
|
// Script blocker button
|
|
|
|
|
updateScriptBlockerButton()
|
|
|
|
|
|
2020-07-31 14:26:10 -07:00
|
|
|
// Enforce dark mode setting
|
|
|
|
|
tab.bridge.darkModeEnabled = toolbarController.darkModeEnabled
|
2021-05-06 16:42:04 -07:00
|
|
|
|
|
|
|
|
// Blur url bar, if applicable
|
|
|
|
|
toolbarController.urlBar.textField.resignFirstResponder()
|
2020-07-22 19:29:38 -07:00
|
|
|
}
|
|
|
|
|
|
2020-07-24 19:26:35 -07:00
|
|
|
override func viewWillAppear(_ animated: Bool) {
|
|
|
|
|
super.viewWillAppear(animated)
|
|
|
|
|
becomeFirstResponder()
|
|
|
|
|
}
|
|
|
|
|
|
2020-07-31 15:25:22 -07:00
|
|
|
override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
|
|
|
|
|
super.traitCollectionDidChange(previousTraitCollection)
|
|
|
|
|
|
|
|
|
|
// Not sure why this doesn't happen automatically...
|
|
|
|
|
toolbarController.traitCollectionDidChange(previousTraitCollection)
|
|
|
|
|
}
|
|
|
|
|
|
2021-02-17 18:28:18 -08:00
|
|
|
internal func updateScriptBlockerButton() {
|
2020-07-31 14:39:18 -07:00
|
|
|
var numBlockedScripts: Int = tab.blockedScriptOrigins.count
|
2020-07-31 15:03:00 -07:00
|
|
|
if tab.url != nil, tab.javaScriptEnabled == false {
|
2020-07-31 14:39:18 -07:00
|
|
|
// Because the page is blocked too, notify.
|
|
|
|
|
numBlockedScripts += 1
|
|
|
|
|
}
|
|
|
|
|
|
2021-10-21 15:08:04 -07:00
|
|
|
var policy: ScriptPolicy? = nil
|
|
|
|
|
if let url = webView.url, let host = url.host {
|
|
|
|
|
policy = policyManager.scriptPolicy(forOrigin: host)
|
2020-07-31 15:03:00 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let iconView = toolbarController.scriptControllerIconView
|
|
|
|
|
iconView.shieldsDown = tab.javaScriptEnabled
|
2021-10-21 15:08:04 -07:00
|
|
|
// iconView.setBlockedScriptsNumber(numBlockedScripts)
|
2021-06-14 15:46:01 -07:00
|
|
|
|
2021-10-21 15:08:04 -07:00
|
|
|
if let policy = policy {
|
|
|
|
|
iconView.currentPolicy = policy
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
iconView.isEnabled = (webView.url != nil)
|
2020-07-22 19:29:38 -07:00
|
|
|
}
|
2020-09-30 15:23:05 -07:00
|
|
|
|
2022-02-21 16:01:01 -08:00
|
|
|
@discardableResult
|
|
|
|
|
public func createNewTab(withURL url: URL?, loadInBackground: Bool = false) -> Tab {
|
2020-09-30 15:23:05 -07:00
|
|
|
let newTab = tabController.createNewTab(url: url)
|
2021-05-06 16:42:04 -07:00
|
|
|
|
2022-02-21 16:01:01 -08:00
|
|
|
if !loadInBackground {
|
|
|
|
|
self.tab = newTab
|
|
|
|
|
|
|
|
|
|
if url == nil && traitCollection.userInterfaceIdiom == .mac {
|
|
|
|
|
self.toolbarController.urlBar.textField.becomeFirstResponder()
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
// Send this message to get it to load NOW, instead of waiting for it to show up
|
|
|
|
|
// in the view hierarchy.
|
|
|
|
|
tab.webView.didMoveToWindow()
|
|
|
|
|
|
|
|
|
|
// Update tab bar now
|
|
|
|
|
updateTabBarVisibility()
|
2021-05-06 16:42:04 -07:00
|
|
|
}
|
2022-02-21 16:01:01 -08:00
|
|
|
|
|
|
|
|
return newTab
|
2020-09-30 15:23:05 -07:00
|
|
|
}
|
2021-02-17 18:28:18 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
extension BrowserViewController: UIPopoverPresentationControllerDelegate
|
|
|
|
|
{
|
2020-07-29 19:24:05 -07:00
|
|
|
func adaptivePresentationStyle(for controller: UIPresentationController, traitCollection: UITraitCollection) -> UIModalPresentationStyle {
|
|
|
|
|
// Forces popovers to present on iPhone
|
|
|
|
|
return .none
|
|
|
|
|
}
|
2021-02-17 18:28:18 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
extension BrowserViewController: ScriptPolicyViewControllerDelegate
|
|
|
|
|
{
|
2020-07-24 19:26:35 -07:00
|
|
|
func didChangeScriptPolicy() {
|
2020-07-30 23:54:20 -07:00
|
|
|
tab.bridge.policyDataSourceDidChange()
|
|
|
|
|
webView.reload()
|
2020-07-24 19:26:35 -07:00
|
|
|
}
|
|
|
|
|
|
2020-07-29 17:46:53 -07:00
|
|
|
func setScriptsEnabledForTab(_ enabled: Bool) {
|
2020-07-30 23:54:20 -07:00
|
|
|
tab.javaScriptEnabled = enabled
|
2020-07-31 15:03:00 -07:00
|
|
|
toolbarController.scriptControllerIconView.shieldsDown = enabled
|
2020-07-29 17:46:53 -07:00
|
|
|
}
|
2021-02-17 18:28:18 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
extension BrowserViewController: AutocompleteViewControllerDelegate
|
|
|
|
|
{
|
2020-09-21 17:56:22 -07:00
|
|
|
func autocompleteController(_: AutocompleteViewController, didSelectHistoryItem item: HistoryItem) {
|
|
|
|
|
tab.beginLoadingURL(item.url)
|
|
|
|
|
autocompleteViewController.view.isHidden = true
|
|
|
|
|
}
|
2021-02-17 18:28:18 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
extension BrowserViewController: TabDelegate
|
|
|
|
|
{
|
|
|
|
|
func didBlockScriptOrigin(_ origin: String, forTab: Tab) {
|
|
|
|
|
updateScriptBlockerButton()
|
2020-09-21 18:35:39 -07:00
|
|
|
}
|
2021-02-17 18:28:18 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
extension BrowserViewController: TabPickerViewControllerDelegate
|
|
|
|
|
{
|
|
|
|
|
func tabPicker(_ picker: TabPickerViewController, didSelectTab tab: Tab) {
|
|
|
|
|
self.tab = tab
|
|
|
|
|
picker.dismiss(animated: true, completion: nil)
|
2020-09-21 18:35:39 -07:00
|
|
|
}
|
2020-09-30 18:06:47 -07:00
|
|
|
|
2021-02-17 18:28:18 -08:00
|
|
|
func tabPicker(_ picker: TabPickerViewController, willCloseTab tab: Tab) {
|
|
|
|
|
// Dismiss picker if current tab is closed using the picker
|
|
|
|
|
if tab == self.tab {
|
|
|
|
|
picker.dismiss(animated: true, completion: nil)
|
2020-11-10 11:57:40 -06:00
|
|
|
}
|
|
|
|
|
}
|
2020-07-22 19:29:38 -07:00
|
|
|
}
|
2021-02-15 22:34:05 -08:00
|
|
|
|
2021-02-15 23:06:23 -08:00
|
|
|
extension BrowserViewController: UITextFieldDelegate
|
|
|
|
|
{
|
|
|
|
|
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
|
|
|
|
|
if let text = textField.text {
|
|
|
|
|
let matches = BrowserHistory.shared.visitedToplevelHistoryItems(matching: text)
|
|
|
|
|
autocompleteViewController.historyItems = matches
|
|
|
|
|
|
|
|
|
|
autocompleteViewController.view.isHidden = matches.count == 0
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
|
|
|
|
|
if let text = textField.text?.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines) {
|
|
|
|
|
|
|
|
|
|
// Dumb rules for stuff that "looks like" a URL
|
|
|
|
|
if !text.contains(" "),
|
|
|
|
|
text.components(separatedBy: ".").count > 1,
|
|
|
|
|
var url = URL(string: text)
|
|
|
|
|
{
|
|
|
|
|
if url.scheme == nil {
|
|
|
|
|
let urlString = "http://\(text)"
|
|
|
|
|
if let fixedURL = URL(string: urlString) {
|
|
|
|
|
url = fixedURL
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
tab.beginLoadingURL(url)
|
|
|
|
|
} else {
|
2021-03-09 12:20:51 -08:00
|
|
|
let searchURL = Settings.shared.searchProvider.provider().searchURLWithQuery(text)
|
2021-02-15 23:06:23 -08:00
|
|
|
tab.beginLoadingURL(searchURL)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
textField.resignFirstResponder()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
override func textFieldShouldEndEditing(_ textField: UITextField) -> Bool {
|
|
|
|
|
true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func textFieldDidEndEditing(_ textField: UITextField) {
|
2021-03-09 00:14:48 -08:00
|
|
|
if !changingFocusToAutocompleteController {
|
|
|
|
|
autocompleteViewController.view.isHidden = true
|
|
|
|
|
}
|
2021-02-15 23:06:23 -08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2021-02-15 22:34:05 -08:00
|
|
|
extension BrowserViewController: ReaderViewControllerDelegate
|
|
|
|
|
{
|
|
|
|
|
func readerViewController(_ reader: ReaderViewController, didRequestNavigationToURL navigationURL: URL) {
|
|
|
|
|
tab.beginLoadingURL(navigationURL)
|
|
|
|
|
}
|
|
|
|
|
}
|
2021-03-09 00:14:48 -08:00
|
|
|
|
|
|
|
|
extension BrowserViewController: URLBarDelegate
|
|
|
|
|
{
|
|
|
|
|
func urlBarRequestedFocusEscape(_ urlBar: URLBar) {
|
|
|
|
|
changingFocusToAutocompleteController = true
|
|
|
|
|
_ = self.autocompleteViewController.becomeFirstResponder()
|
|
|
|
|
changingFocusToAutocompleteController = false
|
|
|
|
|
}
|
|
|
|
|
}
|
2021-05-06 16:19:39 -07:00
|
|
|
|
|
|
|
|
extension BrowserViewController: MFMailComposeViewControllerDelegate
|
|
|
|
|
{
|
|
|
|
|
func mailComposeController(_ controller: MFMailComposeViewController, didFinishWith result: MFMailComposeResult, error: Error?) {
|
|
|
|
|
controller.dismiss(animated: true, completion: nil)
|
|
|
|
|
}
|
|
|
|
|
}
|
2022-02-21 16:01:01 -08:00
|
|
|
|
|
|
|
|
extension BrowserViewController: TabControllerDelegate
|
|
|
|
|
{
|
|
|
|
|
func tabController(_ controller: TabController, didUpdateTitle: String, forTab tab: Tab) {
|
|
|
|
|
updateTitleAndURL(forWebView: tab.webView)
|
|
|
|
|
|
|
|
|
|
// Fetch favicon in background, if applicable
|
|
|
|
|
if tab.favicon == nil, let url = tab.webView.url {
|
|
|
|
|
tab.updateFaviconForURL(url)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func tabController(_ controller: TabController, didUpdateFavicon: UIImage?, forTab tab: Tab) {
|
|
|
|
|
updateTitleAndURL(forWebView: tab.webView)
|
|
|
|
|
}
|
|
|
|
|
}
|