Script blocking UI works now

This commit is contained in:
James Magahern
2020-07-24 19:26:35 -07:00
parent 125c7f8991
commit 37eeeacc85
16 changed files with 619 additions and 34 deletions

View File

@@ -5,19 +5,59 @@
// Created by James Magahern on 7/21/20.
//
import Combine
import UIKit
import WebKit
class BrowserView: UIView
{
var webView: WKWebView? {
didSet { addSubview(webView!); setNeedsLayout() }
var toolbarView: ToolbarView? {
didSet { addSubview(toolbarView!) }
}
var webView: WKWebView? {
didSet {
if let toolbarView = toolbarView {
insertSubview(webView!, belowSubview: toolbarView)
} else {
addSubview(webView!)
}
}
}
var keyboardWillShowObserver: AnyCancellable?
var keyboardWillHideObserver: AnyCancellable?
var keyboardLayoutOffset: CGFloat = 0 { didSet { setNeedsLayout() } }
convenience init() {
self.init(frame: .zero)
keyboardWillShowObserver = NotificationCenter.default.publisher(for: UIWindow.keyboardWillShowNotification).sink { notification in
if let keyboardFrame = notification.userInfo?[UIWindow.keyboardFrameEndUserInfoKey] as? CGRect {
self.keyboardLayoutOffset = self.bounds.height - keyboardFrame.minY
}
}
keyboardWillHideObserver = NotificationCenter.default.publisher(for: UIWindow.keyboardWillHideNotification).sink { notification in
if let keyboardFrame = notification.userInfo?[UIWindow.keyboardFrameEndUserInfoKey] as? CGRect {
self.keyboardLayoutOffset = self.bounds.height - keyboardFrame.minY
}
}
}
override func layoutSubviews()
{
super.layoutSubviews()
webView?.frame = bounds
if let toolbarView = toolbarView {
var toolbarSize = toolbarView.sizeThatFits(bounds.size)
if keyboardLayoutOffset == 0 {
toolbarSize.height += safeAreaInsets.bottom
}
toolbarView.bounds = CGRect(origin: .zero, size: toolbarSize)
toolbarView.center = CGPoint(x: bounds.center.x, y: bounds.maxY - (toolbarView.bounds.height / 2) - keyboardLayoutOffset)
}
}
}

View File

@@ -7,59 +7,114 @@
import UIKit
class BrowserViewController: UIViewController, SBRProcessBundleBridgeDelegate
class BrowserViewController: UIViewController,
SBRProcessBundleBridgeDelegate, WKNavigationDelegate,
UITextFieldDelegate, ScriptPolicyViewControllerDelegate
{
let bridge = SBRProcessBundleBridge()
let browserView = BrowserView()
private let policyManager = ResourcePolicyManager()
private let toolbarController = ToolbarViewController()
private var blockedScriptOrigins = Set<String>()
private var scriptBlockerButtonItem: UIBarButtonItem
override var canBecomeFirstResponder: Bool { true }
init()
{
scriptBlockerButtonItem = UIBarButtonItem(title: "0", image: nil, primaryAction: UIAction(handler: { action in
// present
}), menu: nil)
init() {
super.init(nibName: nil, bundle: nil)
}
required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") }
override func loadView()
{
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
webView.reload()
}), for: .touchUpInside)
// Script button
toolbarController.scriptControllerIconView.addAction(UIAction(handler: { action in
let scriptViewController = ScriptPolicyViewController(policyManager: self.policyManager, blockedScripts: self.blockedScriptOrigins)
scriptViewController.delegate = self
let navController = UINavigationController(rootViewController: scriptViewController)
self.present(navController, animated: true, completion: nil)
}), for: .touchUpInside)
// TextField delegate
toolbarController.urlBar.textField.delegate = self
self.view = browserView
}
override func viewDidLoad()
{
let request = URLRequest(url: URL(string: "https://yahoo.com")!)
browserView.webView?.load(request)
setToolbarItems([ scriptBlockerButtonItem ], animated: false)
override func viewDidLoad() {
beginLoadingURL(URL(string: "https://reddit.com")!)
}
private func updateScriptBlockerButton()
{
scriptBlockerButtonItem.title = "\(blockedScriptOrigins.count)"
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, didBlockScriptResourceFromOrigin origin: String)
{
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
}
}
// 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()
}
}

View File

@@ -0,0 +1,109 @@
//
// ToolbarViewController.swift
// SBrowser
//
// Created by James Magahern on 7/23/20.
//
import UIKit
class ToolbarButtonView: UIView
{
private var buttonPadding = CGFloat(8.0)
private var buttonViews: [UIView] = []
func addButtonView(_ button: UIView) {
buttonViews.append(button)
addSubview(button)
setNeedsLayout()
}
override func sizeThatFits(_ size: CGSize) -> CGSize {
let width: CGFloat = buttonViews.reduce(0.0) { (result, button) -> CGFloat in
return result + button.sizeThatFits(size).width + buttonPadding
}
return CGSize(width: width, height: size.height)
}
override func layoutSubviews() {
var buttonRect = CGRect(origin: .zero, size: CGSize(width: 0, height: bounds.height))
buttonRect.origin.x = buttonPadding
for button in buttonViews {
let buttonSize = button.sizeThatFits(bounds.size)
buttonRect.size = CGSize(width: buttonSize.width, height: bounds.height)
button.frame = buttonRect
buttonRect.origin.x += buttonRect.width + buttonPadding
}
}
}
class ToolbarView: UIView
{
var urlBar: URLBar? { didSet { containerView.addSubview(urlBar!) } }
let containerView = UIView(frame: .zero)
let backgroundView = UIVisualEffectView(effect: UIBlurEffect(style: .systemThickMaterial))
let buttonsView = ToolbarButtonView(frame: .zero)
convenience init()
{
self.init(frame: .zero)
addSubview(backgroundView)
addSubview(containerView)
containerView.addSubview(buttonsView)
}
override func sizeThatFits(_ size: CGSize) -> CGSize
{
return CGSize(width: size.width, height: 44.0)
}
override func layoutSubviews()
{
super.layoutSubviews()
backgroundView.frame = bounds
var containerBounds = bounds
containerBounds.size.height -= safeAreaInsets.bottom
containerView.frame = containerBounds
containerView.frame = containerView.frame.insetBy(dx: 8.0, dy: 4.0)
let toolbarSize = buttonsView.sizeThatFits(containerView.bounds.size)
if let urlBar = urlBar {
urlBar.frame = CGRect(origin: .zero, size: CGSize(width: containerView.bounds.width - toolbarSize.width, height: toolbarSize.height))
}
buttonsView.frame = CGRect(origin: CGPoint(x: urlBar?.frame.maxX ?? 0 + 8.0, y: 0), size: toolbarSize)
}
}
class ToolbarViewController: UIViewController
{
let urlBar = URLBar()
let toolbarView = ToolbarView()
let scriptControllerIconView = ScriptControllerIconView()
let shareButton = UIButton(frame: .zero)
init() {
super.init(nibName: nil, bundle: nil)
toolbarView.urlBar = urlBar
shareButton.setImage(UIImage(systemName: "square.and.arrow.up"), for: .normal)
toolbarView.buttonsView.addButtonView(shareButton)
toolbarView.buttonsView.addButtonView(scriptControllerIconView)
}
override func loadView() {
self.view = toolbarView
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}

View File

@@ -0,0 +1,58 @@
//
// URLBar.swift
// SBrowser
//
// Created by James Magahern on 7/23/20.
//
import UIKit
class URLBar: UIView
{
let textField = UITextField(frame: .zero)
let refreshButton = UIButton(frame: .zero)
private let backgroundView = UIVisualEffectView(effect: UIBlurEffect(style: .systemThickMaterial))
convenience init() {
self.init(frame: .zero)
backgroundColor = .clear
backgroundView.layer.masksToBounds = true
backgroundView.layer.cornerRadius = 8
backgroundView.layer.borderWidth = 1
backgroundView.layer.borderColor = UIColor.systemFill.cgColor
backgroundView.isUserInteractionEnabled = false
addSubview(backgroundView)
textField.backgroundColor = .clear
textField.textContentType = .URL
textField.keyboardType = .webSearch
textField.autocorrectionType = .no
textField.autocapitalizationType = .none
textField.font = .preferredFont(forTextStyle: .body)
textField.clearingBehavior = .clearOnInsertionAndShowSelectionTint
addSubview(textField)
refreshButton.tintColor = .secondaryLabel
refreshButton.setImage(UIImage(systemName: "arrow.clockwise"), for: .normal)
addSubview(refreshButton)
}
override var intrinsicContentSize: CGSize
{
let preferredHeight = CGFloat(34)
return CGSize(width: 1000.0, height: preferredHeight)
}
override func layoutSubviews()
{
super.layoutSubviews()
backgroundView.frame = bounds
textField.frame = bounds.insetBy(dx: 6.0, dy: 0)
let refreshButtonSize = CGSize(width: textField.frame.height, height: textField.frame.height)
refreshButton.frame = CGRect(origin: CGPoint(x: bounds.width - refreshButtonSize.width, y: 0), size: refreshButtonSize)
}
}