Script blocking UI works now
This commit is contained in:
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
109
SBrowser/Browser View/ToolbarViewController.swift
Normal file
109
SBrowser/Browser View/ToolbarViewController.swift
Normal 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")
|
||||
}
|
||||
}
|
||||
58
SBrowser/Browser View/URLBar.swift
Normal file
58
SBrowser/Browser View/URLBar.swift
Normal 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)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user