// // BrowserViewController.swift // SBrowser // // Created by James Magahern on 7/21/20. // import UIKit class BrowserViewController: UIViewController, SBRProcessBundleBridgeDelegate, WKNavigationDelegate, UITextFieldDelegate, ScriptPolicyViewControllerDelegate { let bridge = SBRProcessBundleBridge() let browserView = BrowserView() var javaScriptEnabledForTab: Bool = false private let policyManager = ResourcePolicyManager() private let toolbarController = ToolbarViewController() private var allowedScriptOrigins = Set() private var blockedScriptOrigins = Set() override var canBecomeFirstResponder: Bool { true } private var titleObservation: NSKeyValueObservation? private var loadingObservation: NSKeyValueObservation? override var preferredStatusBarStyle: UIStatusBarStyle { .lightContent } init() { super.init(nibName: nil, bundle: nil) } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } override func loadView() { bridge.delegate = self bridge.policyDataSource = policyManager let webView = bridge.webView webView.allowsBackForwardNavigationGestures = true webView.navigationDelegate = self browserView.webView = webView browserView.toolbarView = toolbarController.toolbarView // Refresh button toolbarController.urlBar.refreshButton.addAction(UIAction(handler: { action in if webView.isLoading { webView.stopLoading() } else { webView.reload() } }), for: .touchUpInside) // Script button toolbarController.scriptControllerIconView.addAction(UIAction(handler: { action in let hostOrigin = webView.url?.host ?? "" let loadedScripts = self.allowedScriptOrigins.union(self.blockedScriptOrigins) let scriptViewController = ScriptPolicyViewController(policyManager: self.policyManager, hostOrigin: hostOrigin, loadedScripts: loadedScripts) scriptViewController.delegate = self scriptViewController.allowScriptsForTab = self.javaScriptEnabledForTab let navController = UINavigationController(rootViewController: scriptViewController) self.present(navController, animated: true, completion: nil) }), for: .touchUpInside) // Dark mode button toolbarController.darkModeButton.addAction(UIAction(handler: { _ in self.bridge.darkModeEnabled = !self.bridge.darkModeEnabled self.toolbarController.darkModeEnabled = self.bridge.darkModeEnabled }), for: .touchUpInside) // TextField delegate toolbarController.urlBar.textField.delegate = self // Load progress loadingObservation = webView.observe(\.estimatedProgress) { (webView, observedChange) in if webView.estimatedProgress == 1.0 { self.toolbarController.urlBar.loadProgress = .complete } else { self.toolbarController.urlBar.loadProgress = .loading(progress: webView.estimatedProgress) } } // Title observer titleObservation = webView.observe(\.title, changeHandler: { (webView, observedChange) in self.browserView.titlebarView.titleLabelView.text = webView.title }) self.view = browserView } override func viewDidLoad() { beginLoadingURL(URL(string: "https://google.com")!) } override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) becomeFirstResponder() } private func updateScriptBlockerButton() { toolbarController.scriptControllerIconView.setBlockedScriptsNumber(blockedScriptOrigins.count) } func beginLoadingURL(_ url: URL) { let request = URLRequest(url: url) bridge.webView.load(request) } // MARK: SBRProcessBundleBridgeDelegate func webProcess(_ bridge: SBRProcessBundleBridge, didAllowScriptResourceFromOrigin origin: String) { print("Allowed script resource from origin: \(origin)") allowedScriptOrigins.formUnion([ origin ]) updateScriptBlockerButton() } func webProcess(_ bridge: SBRProcessBundleBridge, didBlockScriptResourceFromOrigin origin: String) { print("Blocked script resource from origin: \(origin)") blockedScriptOrigins.formUnion([ origin ]) updateScriptBlockerButton() } // MARK: Navigation Delegate func webView(_ webView: WKWebView, didStartProvisionalNavigation navigation: WKNavigation!) { // Reset tracking this blockedScriptOrigins.removeAll() if let urlString = webView.url?.absoluteString { toolbarController.urlBar.textField.text = urlString } } 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 = javaScriptEnabledForTab if !allowJavaScript, let host = navigationAction.request.url?.host { // Check origin policy allowJavaScript = policyManager.allowedOriginsForScriptResources().contains(host) } preferences.allowsContentJavaScript = allowJavaScript decisionHandler(.allow, preferences) } // 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) { beginLoadingURL(url) } } } textField.resignFirstResponder() return false } // MARK: Script Policy View Controller Delegate func didChangeScriptPolicy() { bridge.policyDataSourceDidChange() bridge.webView.reload() } func setScriptsEnabledForTab(_ enabled: Bool) { javaScriptEnabledForTab = enabled bridge.allowAllScripts = enabled } }