706 lines
29 KiB
Swift
706 lines
29 KiB
Swift
//
|
|
// BrowserViewController.swift
|
|
// SBrowser
|
|
//
|
|
// Created by James Magahern on 7/21/20.
|
|
//
|
|
|
|
import Combine
|
|
import UIKit
|
|
import UniformTypeIdentifiers
|
|
|
|
class BrowserViewController: UIViewController, WKNavigationDelegate, WKUIDelegate,
|
|
UITextFieldDelegate, ScriptPolicyViewControllerDelegate,
|
|
UIPopoverPresentationControllerDelegate, TabDelegate, TabPickerViewControllerDelegate,
|
|
AutocompleteViewControllerDelegate, ShortcutResponder
|
|
{
|
|
let browserView = BrowserView()
|
|
var tab: Tab { didSet { didChangeTab(tab) } }
|
|
var webView: WKWebView { tab.webView }
|
|
|
|
private let tabController = TabController()
|
|
private let tabBarViewController: TabBarViewController
|
|
private let toolbarController = ToolbarViewController()
|
|
private let findOnPageController = FindOnPageViewController()
|
|
|
|
private let autocompleteViewController = AutocompleteViewController()
|
|
private let redirectRules = PersonalRedirectRules()
|
|
|
|
private var policyManager: ResourcePolicyManager { tabController.policyManager }
|
|
|
|
override var canBecomeFirstResponder: Bool { true }
|
|
|
|
private var titleObservation: NSKeyValueObservation?
|
|
private var loadingObservation: NSKeyValueObservation?
|
|
private var backButtonObservation: NSKeyValueObservation?
|
|
private var forwardButtonObservation: NSKeyValueObservation?
|
|
private var activeTabObservation: AnyCancellable?
|
|
|
|
private var loadError: Error?
|
|
|
|
private var commandKeyHeld: Bool = false
|
|
|
|
override var preferredStatusBarStyle: UIStatusBarStyle { .lightContent }
|
|
|
|
init() {
|
|
self.tab = tabController.tabs.first!
|
|
self.tabBarViewController = TabBarViewController(tabController: tabController)
|
|
super.init(nibName: nil, bundle: nil)
|
|
|
|
addChild(toolbarController)
|
|
addChild(findOnPageController)
|
|
addChild(tabBarViewController)
|
|
|
|
didChangeTab(tab)
|
|
}
|
|
|
|
required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") }
|
|
|
|
override func loadView() {
|
|
browserView.toolbarView = toolbarController.toolbarView
|
|
browserView.findOnPageView = findOnPageController.findOnPageView
|
|
browserView.tabBarView = tabBarViewController.tabBarView
|
|
|
|
// Refresh button
|
|
toolbarController.urlBar.refreshButton.addAction(UIAction(handler: { [unowned self] action in
|
|
if self.webView.isLoading {
|
|
self.webView.stopLoading()
|
|
} else {
|
|
self.webView.reload()
|
|
}
|
|
}), for: .touchUpInside)
|
|
|
|
// Back button
|
|
toolbarController.backButton.addAction(UIAction(handler: { [unowned self] _ in
|
|
self.webView.goBack()
|
|
}), for: .touchUpInside)
|
|
|
|
// Forward button
|
|
toolbarController.forwardButton.addAction(UIAction(handler: { [unowned self] _ in
|
|
self.webView.goForward()
|
|
}), for: .touchUpInside)
|
|
|
|
// Share button
|
|
toolbarController.shareButton.addAction(UIAction(handler: { [unowned self, toolbarController] _ in
|
|
if let url = self.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 self.webView.title
|
|
case .messageBody: return self.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)
|
|
}
|
|
}), 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)
|
|
scriptViewController.delegate = self
|
|
|
|
let navController = UINavigationController(rootViewController: scriptViewController)
|
|
navController.modalPresentationStyle = .popover
|
|
navController.popoverPresentationController?.sourceView = self.toolbarController.scriptControllerIconView
|
|
navController.popoverPresentationController?.delegate = self
|
|
|
|
self.present(navController, animated: true, completion: nil)
|
|
}), for: .touchUpInside)
|
|
|
|
// Dark mode button
|
|
toolbarController.darkModeButton.addAction(UIAction(handler: { [unowned self] _ in
|
|
self.tab.bridge.darkModeEnabled = !self.tab.bridge.darkModeEnabled
|
|
self.toolbarController.darkModeEnabled = self.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)
|
|
}), for: .touchUpInside)
|
|
|
|
let newTabAction = UIAction { [unowned self] action in
|
|
if let gestureRecognizer = action.sender as? UILongPressGestureRecognizer {
|
|
if gestureRecognizer.state != .began { return }
|
|
}
|
|
|
|
// Create new tab
|
|
let newTab = tabController.createNewTab(url: nil)
|
|
self.tab = newTab
|
|
}
|
|
|
|
let gestureRecognizer = UILongPressGestureRecognizer(action: newTabAction)
|
|
toolbarController.windowButton.addGestureRecognizer(gestureRecognizer)
|
|
|
|
// New tab button
|
|
toolbarController.newTabButton.addAction(newTabAction, for: .touchUpInside)
|
|
|
|
// Error button
|
|
toolbarController.urlBar.errorButton.addAction(UIAction(handler: { [unowned self] _ in
|
|
let alert = UIAlertController(title: "Error", message: self.loadError?.localizedDescription, preferredStyle: .actionSheet)
|
|
alert.popoverPresentationController?.sourceView = self.toolbarController.urlBar.errorButton
|
|
|
|
alert.addAction(UIAlertAction(title: "Reload", style: .destructive, handler: { _ in
|
|
self.webView.reload()
|
|
alert.dismiss(animated: true, completion: nil)
|
|
}))
|
|
|
|
alert.addAction(UIAlertAction(title: "OK", style: .default, handler: { _ in
|
|
alert.dismiss(animated: true, completion: nil)
|
|
|
|
// Clears out the error state
|
|
toolbarController.urlBar.loadProgress = .complete
|
|
}))
|
|
|
|
self.present(alert, animated: true, completion: nil)
|
|
}), for: .touchUpInside)
|
|
|
|
// Cancel button: hide autocomplete if applicable
|
|
toolbarController.toolbarView.cancelButton.addAction(UIAction(handler: { [unowned self] _ in
|
|
self.autocompleteViewController.view.isHidden = true
|
|
}), for: .touchUpInside)
|
|
|
|
// TextField delegate
|
|
toolbarController.urlBar.textField.delegate = self
|
|
|
|
// Autocomplete controller
|
|
autocompleteViewController.delegate = self
|
|
autocompleteViewController.view.isHidden = true
|
|
|
|
// Font size adjust
|
|
toolbarController.urlBar.documentButton.addAction(UIAction(handler: { [unowned self] _ in
|
|
let documentControls = DocumentControlViewController()
|
|
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)
|
|
|
|
// Font size adjust
|
|
documentControls.fontSizeAdjustView.decreaseSizeButton.addAction(UIAction(handler: { [unowned self] sender in
|
|
self.decreaseSize(sender)
|
|
label.text = numberFormatter.string(for: tab.webView._viewScale)
|
|
}), for: .touchUpInside)
|
|
|
|
documentControls.fontSizeAdjustView.increaseSizeButton.addAction(UIAction(handler: { [unowned self] sender in
|
|
self.increaseSize(sender)
|
|
label.text = numberFormatter.string(for: tab.webView._viewScale)
|
|
}), for: .touchUpInside)
|
|
|
|
// Find on page
|
|
documentControls.findOnPageControlView.addAction(UIAction(handler: { [unowned self] _ in
|
|
documentControls.dismiss(animated: true, completion: nil)
|
|
browserView.setFindOnPageVisible(true, animated: true)
|
|
}), for: .touchUpInside)
|
|
|
|
// Navigation controls
|
|
documentControls.navigationControlView.backButton.isEnabled = webView.canGoBack
|
|
documentControls.navigationControlView.backButton.addAction(UIAction() { [unowned self] _ in
|
|
webView.goBack()
|
|
}, for: .touchUpInside)
|
|
|
|
documentControls.observations.append(webView.observe(\.canGoBack, changeHandler: { (_, _) in
|
|
documentControls.navigationControlView.backButton.isEnabled = webView.canGoBack
|
|
}))
|
|
|
|
documentControls.navigationControlView.forwardButton.isEnabled = webView.canGoForward
|
|
documentControls.navigationControlView.forwardButton.addAction(UIAction() { [unowned self] _ in
|
|
webView.goForward()
|
|
}, for: .touchUpInside)
|
|
|
|
documentControls.observations.append(webView.observe(\.canGoForward, changeHandler: { (_, _) in
|
|
documentControls.navigationControlView.forwardButton.isEnabled = webView.canGoForward
|
|
}))
|
|
|
|
present(documentControls, animated: true, completion: nil)
|
|
}), for: .touchUpInside)
|
|
|
|
// Find on page dismiss
|
|
findOnPageController.findOnPageView.doneButton.addAction(UIAction(handler: { [unowned self] _ in
|
|
browserView.setFindOnPageVisible(false, animated: true)
|
|
}), for: .touchUpInside)
|
|
|
|
// Tab controller
|
|
activeTabObservation = tabController.$activeTabIndex
|
|
.receive(on: RunLoop.main)
|
|
.sink(receiveValue: { [unowned self] (activeTab: Int) in
|
|
if activeTab < tabController.tabs.count {
|
|
let tab = tabController.tabs[activeTab]
|
|
if self.tab != tab {
|
|
self.tab = tab
|
|
}
|
|
}
|
|
})
|
|
|
|
self.view = browserView
|
|
}
|
|
|
|
private func updateLoadProgress(forWebView webView: WKWebView) {
|
|
if let loadError = loadError {
|
|
toolbarController.urlBar.loadProgress = .error(error: loadError)
|
|
} else 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.setTitle(webView.title ?? "")
|
|
|
|
if let urlString = webView.url?.absoluteString {
|
|
toolbarController.urlBar.textField.text = urlString
|
|
} else {
|
|
toolbarController.urlBar.textField.text = ""
|
|
}
|
|
|
|
// 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)
|
|
}
|
|
}
|
|
|
|
private func didChangeTab(_ tab: Tab) {
|
|
if let activeIndex = tabController.tabs.firstIndex(of: tab) {
|
|
tabController.activeTabIndex = activeIndex
|
|
}
|
|
|
|
tab.delegate = self
|
|
|
|
let webView = tab.webView
|
|
webView.allowsBackForwardNavigationGestures = true
|
|
webView.navigationDelegate = self
|
|
webView.uiDelegate = self
|
|
|
|
// Change webView
|
|
browserView.webView = webView
|
|
findOnPageController.webView = webView
|
|
|
|
// Autocomplete view
|
|
browserView.autocompleteView = autocompleteViewController.view
|
|
|
|
// Show tab bar view?
|
|
browserView.tabBarViewVisible = tabController.tabs.count > 1
|
|
|
|
// Color theme
|
|
browserView.titlebarView.setColorTheme(tab.colorTheme)
|
|
|
|
// Load progress
|
|
updateLoadProgress(forWebView: webView)
|
|
loadingObservation = webView.observe(\.estimatedProgress) { [unowned self] (webView, observedChange) in
|
|
self.updateLoadProgress(forWebView: webView)
|
|
}
|
|
|
|
// 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
|
|
})
|
|
|
|
// Script blocker button
|
|
updateScriptBlockerButton()
|
|
|
|
// Enforce dark mode setting
|
|
tab.bridge.darkModeEnabled = toolbarController.darkModeEnabled
|
|
}
|
|
|
|
override func viewWillAppear(_ animated: Bool) {
|
|
super.viewWillAppear(animated)
|
|
becomeFirstResponder()
|
|
}
|
|
|
|
override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
|
|
super.traitCollectionDidChange(previousTraitCollection)
|
|
|
|
// Not sure why this doesn't happen automatically...
|
|
toolbarController.traitCollectionDidChange(previousTraitCollection)
|
|
}
|
|
|
|
private func updateCommandKeyState(forPresses presses: Set<UIPress>) {
|
|
guard let press = presses.first else { return }
|
|
|
|
if let key = press.key {
|
|
if key.modifierFlags == [.command] {
|
|
let isDown = press.phase == .began || press.phase == .changed || press.phase == .stationary
|
|
self.commandKeyHeld = isDown
|
|
}
|
|
}
|
|
}
|
|
|
|
override func pressesBegan(_ presses: Set<UIPress>, with event: UIPressesEvent?) {
|
|
super.pressesBegan(presses, with: event)
|
|
updateCommandKeyState(forPresses: presses)
|
|
}
|
|
|
|
override func pressesCancelled(_ presses: Set<UIPress>, with event: UIPressesEvent?) {
|
|
super.pressesCancelled(presses, with: event)
|
|
updateCommandKeyState(forPresses: presses)
|
|
}
|
|
|
|
override func pressesEnded(_ presses: Set<UIPress>, with event: UIPressesEvent?) {
|
|
super.pressesEnded(presses, with: event)
|
|
updateCommandKeyState(forPresses: presses)
|
|
}
|
|
|
|
private func updateScriptBlockerButton() {
|
|
var numBlockedScripts: Int = tab.blockedScriptOrigins.count
|
|
if tab.url != nil, tab.javaScriptEnabled == false {
|
|
// Because the page is blocked too, notify.
|
|
numBlockedScripts += 1
|
|
}
|
|
|
|
var scriptsAllowedForHost = false
|
|
if let url = webView.url, let host = url.host, policyManager.allowedOriginsForScriptResources().contains(host) {
|
|
scriptsAllowedForHost = true
|
|
}
|
|
|
|
let iconView = toolbarController.scriptControllerIconView
|
|
iconView.shieldsDown = tab.javaScriptEnabled
|
|
iconView.someScriptsAllowed = scriptsAllowedForHost
|
|
iconView.setBlockedScriptsNumber(numBlockedScripts)
|
|
}
|
|
|
|
public func createNewTab(withURL url: URL?) {
|
|
let newTab = tabController.createNewTab(url: url)
|
|
self.tab = newTab
|
|
}
|
|
|
|
// MARK: UIPopoverPresentationControllerDelegate
|
|
|
|
func adaptivePresentationStyle(for controller: UIPresentationController, traitCollection: UITraitCollection) -> UIModalPresentationStyle {
|
|
// Forces popovers to present on iPhone
|
|
return .none
|
|
}
|
|
|
|
// MARK: Navigation Delegate
|
|
|
|
func webView(_ webView: WKWebView, didStartProvisionalNavigation navigation: WKNavigation!) {
|
|
loadError = nil
|
|
|
|
// Check to make sure we have connected to the web content process
|
|
if !tab.bridge.webContentProcessConnected {
|
|
// This means we started loading a page but the web content process hasn't loaded, which means
|
|
// scripts are not getting blocked.
|
|
|
|
// If you're ad-hoc signing this, you'll need to disable library validation:
|
|
// sudo defaults write /Library/Preferences/com.apple.security.libraryvalidation DisableLibraryValidation -bool YES
|
|
|
|
DispatchQueue.main.async { [unowned self] in
|
|
// Stop loading now
|
|
webView.stopLoading()
|
|
|
|
// Show an alert
|
|
let alert = UIAlertController(title: "Web Process Not Loaded",
|
|
message: "The web content process never contacted the host application",
|
|
preferredStyle: .alert)
|
|
alert.addAction(UIAlertAction(title: "OK", style: .default, handler: { _ in
|
|
alert.dismiss(animated: true, completion: nil)
|
|
}))
|
|
|
|
present(alert, animated: true, completion: nil)
|
|
}
|
|
}
|
|
|
|
// Reset tracking this
|
|
tab.allowedScriptOrigins.removeAll()
|
|
tab.blockedScriptOrigins.removeAll()
|
|
updateScriptBlockerButton()
|
|
|
|
// Blur url bar if applicable
|
|
toolbarController.urlBar.textField.resignFirstResponder()
|
|
|
|
updateTitleAndURL(forWebView: webView)
|
|
|
|
if let url = webView.url {
|
|
// Start requesting favicon
|
|
tab.updateFaviconForURL(url)
|
|
}
|
|
}
|
|
|
|
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
|
|
toolbarController.urlBar.loadProgress = .complete
|
|
|
|
// Update history
|
|
if let url = webView.url {
|
|
let title = webView.title ?? ""
|
|
BrowserHistory.shared.didNavigate(toURL: url, title: title)
|
|
}
|
|
}
|
|
|
|
func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, preferences: WKWebpagePreferences, decisionHandler: @escaping (WKNavigationActionPolicy, WKWebpagePreferences) -> Void)
|
|
{
|
|
// Handle command+click
|
|
if commandKeyHeld && navigationAction.navigationType == .linkActivated {
|
|
// Cancel navigation in this tab
|
|
decisionHandler(.cancel, preferences)
|
|
|
|
// Start navigation in a new tab
|
|
let tab = tabController.createNewTab(url: navigationAction.request.url)
|
|
self.tab = tab
|
|
|
|
// Reset this flag.
|
|
commandKeyHeld = false
|
|
|
|
return
|
|
}
|
|
|
|
var allowJavaScript = tab.javaScriptEnabled
|
|
if !allowJavaScript, let host = navigationAction.request.url?.host {
|
|
// Check origin policy
|
|
allowJavaScript = policyManager.allowedOriginsForScriptResources().contains(host)
|
|
}
|
|
|
|
preferences.allowsContentJavaScript = allowJavaScript
|
|
|
|
if let url = navigationAction.request.url,
|
|
let redirectedURL = redirectRules.redirectedURL(for: url)
|
|
{
|
|
tab.beginLoadingURL(redirectedURL)
|
|
decisionHandler(.cancel, preferences)
|
|
} else {
|
|
decisionHandler(.allow, preferences)
|
|
}
|
|
}
|
|
|
|
func webView(_ webView: WKWebView, didFail navigation: WKNavigation!, withError error: Error) {
|
|
self.loadError = error
|
|
}
|
|
|
|
func webView(_ webView: WKWebView, didFailProvisionalNavigation navigation: WKNavigation!, withError error: Error) {
|
|
self.loadError = error
|
|
}
|
|
|
|
// MARK: WKUIDelegate
|
|
|
|
func webView(_ webView: WKWebView, createWebViewWith configuration: WKWebViewConfiguration, for navigationAction: WKNavigationAction, windowFeatures: WKWindowFeatures) -> WKWebView?
|
|
{
|
|
let newTab = tabController.createNewTab(url: nil, webViewConfiguration: configuration)
|
|
newTab.webView.load(navigationAction.request)
|
|
|
|
self.tab = newTab
|
|
|
|
return newTab.webView
|
|
}
|
|
|
|
func webView(_ webView: WKWebView, contextMenuConfigurationForElement elementInfo: WKContextMenuElementInfo, completionHandler: @escaping (UIContextMenuConfiguration?) -> Void) {
|
|
let menuConfig = UIContextMenuConfiguration(identifier: nil,
|
|
previewProvider: nil) { (menuElements: [UIMenuElement]) -> UIMenu? in
|
|
|
|
let openInNewTab = UIAction(title: "Open in New Tab",
|
|
image: UIImage(systemName: "plus.app"),
|
|
identifier: nil,
|
|
discoverabilityTitle: nil,
|
|
attributes: [],
|
|
state: .off) { [unowned self] _ in
|
|
let newTab = tabController.createNewTab(url: elementInfo.linkURL)
|
|
self.tab = newTab
|
|
}
|
|
|
|
|
|
return UIMenu(title: elementInfo.linkURL?.absoluteString ?? "Link",
|
|
image: nil,
|
|
identifier: nil,
|
|
options: .displayInline,
|
|
children: [ openInNewTab ] + menuElements)
|
|
}
|
|
|
|
completionHandler(menuConfig)
|
|
}
|
|
|
|
// MARK: UITextField Delegate
|
|
|
|
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 {
|
|
// Assume google search
|
|
let queryString = text
|
|
.replacingOccurrences(of: " ", with: "+")
|
|
.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)!
|
|
|
|
let searchURL = URL(string: "https://google.com/search?q=\(queryString)&gbv=1")! // gbv=1: no JS
|
|
tab.beginLoadingURL(searchURL)
|
|
}
|
|
|
|
textField.resignFirstResponder()
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
func textFieldDidEndEditing(_ textField: UITextField) {
|
|
autocompleteViewController.view.isHidden = true
|
|
}
|
|
|
|
// 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)
|
|
}
|
|
}
|
|
|
|
// MARK: Script Policy View Controller Delegate
|
|
|
|
func didChangeScriptPolicy() {
|
|
tab.bridge.policyDataSourceDidChange()
|
|
webView.reload()
|
|
}
|
|
|
|
func setScriptsEnabledForTab(_ enabled: Bool) {
|
|
tab.javaScriptEnabled = enabled
|
|
toolbarController.scriptControllerIconView.shieldsDown = enabled
|
|
}
|
|
|
|
// MARK: Autocomplete Controller Delegate
|
|
|
|
func autocompleteController(_: AutocompleteViewController, didSelectHistoryItem item: HistoryItem) {
|
|
tab.beginLoadingURL(item.url)
|
|
autocompleteViewController.view.isHidden = true
|
|
}
|
|
|
|
// MARK: Keyboard shortcuts
|
|
|
|
func focusURLBar(_ sender: Any?) {
|
|
toolbarController.urlBar.textField.becomeFirstResponder()
|
|
}
|
|
|
|
func goBack(_ sender: Any?) {
|
|
tab.webView.goBack()
|
|
}
|
|
|
|
func goForward(_ sender: Any?) {
|
|
tab.webView.goForward()
|
|
}
|
|
|
|
func createTab(_ sender: Any?) {
|
|
createNewTab(withURL: nil)
|
|
}
|
|
|
|
func previousTab(_ sender: Any?) {
|
|
if let tabIndex = tabController.tabs.firstIndex(of: self.tab) {
|
|
if tabIndex - 1 >= 0 {
|
|
self.tab = tabController.tabs[tabIndex - 1]
|
|
}
|
|
}
|
|
}
|
|
|
|
func nextTab(_ sender: Any?) {
|
|
if let tabIndex = tabController.tabs.firstIndex(of: self.tab) {
|
|
if tabIndex + 1 < tabController.tabs.count {
|
|
self.tab = tabController.tabs[tabIndex + 1]
|
|
}
|
|
}
|
|
}
|
|
|
|
func closeTab(_ sender: Any?) {
|
|
if tabController.tabs.count > 1 {
|
|
tabController.closeTab(self.tab)
|
|
} else {
|
|
#if targetEnvironment(macCatalyst)
|
|
if let originWindowScene = self.view.window?.windowScene {
|
|
UIApplication.shared.requestSceneSessionDestruction(originWindowScene.session, options: nil) { error in
|
|
print("Error when requesting scene destruction: " + error.localizedDescription)
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
}
|
|
|
|
func findOnPage(_ sender: Any?) {
|
|
browserView.setFindOnPageVisible(true, animated: true)
|
|
findOnPageController.findOnPageView.textField.becomeFirstResponder()
|
|
}
|
|
|
|
func refresh(_ sender: Any?) {
|
|
webView.reload()
|
|
}
|
|
|
|
override func increaseSize(_ sender: Any?) {
|
|
tab.webView._viewScale += 0.10
|
|
}
|
|
|
|
override func decreaseSize(_ sender: Any?) {
|
|
tab.webView._viewScale -= 0.10
|
|
}
|
|
}
|