Script blocking UI works now
This commit is contained in:
@@ -18,6 +18,13 @@
|
||||
1ADFF4A724C8C271006DC7AE /* SBrowserProcessBundle.bundle in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = 1ADFF48124C8C12F006DC7AE /* SBrowserProcessBundle.bundle */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
|
||||
1ADFF4AA24C8D477006DC7AE /* SBRProcessPlugin.m in Sources */ = {isa = PBXBuildFile; fileRef = 1ADFF4A924C8D477006DC7AE /* SBRProcessPlugin.m */; };
|
||||
1ADFF4AE24C8ED32006DC7AE /* ResourcePolicyManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1ADFF4AD24C8ED32006DC7AE /* ResourcePolicyManager.swift */; };
|
||||
1ADFF4C024CA6964006DC7AE /* URLBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1ADFF4BF24CA6964006DC7AE /* URLBar.swift */; };
|
||||
1ADFF4C324CA6AF6006DC7AE /* CGPoint+Utils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1ADFF4C224CA6AF6006DC7AE /* CGPoint+Utils.swift */; };
|
||||
1ADFF4C724CA6DEB006DC7AE /* UIEdgeInsets+Layout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1ADFF4C624CA6DEB006DC7AE /* UIEdgeInsets+Layout.swift */; };
|
||||
1ADFF4C924CA793E006DC7AE /* ToolbarViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1ADFF4C824CA793E006DC7AE /* ToolbarViewController.swift */; };
|
||||
1ADFF4CB24CB8278006DC7AE /* ScriptControllerIconView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1ADFF4CA24CB8278006DC7AE /* ScriptControllerIconView.swift */; };
|
||||
1ADFF4CD24CBB0C8006DC7AE /* ScriptPolicyViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1ADFF4CC24CBB0C8006DC7AE /* ScriptPolicyViewController.swift */; };
|
||||
1ADFF4D024CBBCD1006DC7AE /* ScriptPolicyControl.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1ADFF4CF24CBBCD1006DC7AE /* ScriptPolicyControl.swift */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXContainerItemProxy section */
|
||||
@@ -65,6 +72,13 @@
|
||||
1ADFF4AB24C8DF62006DC7AE /* SBRWebProcessDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SBRWebProcessDelegate.h; sourceTree = "<group>"; };
|
||||
1ADFF4AC24C8DFEE006DC7AE /* SBRWebProcessProxy.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SBRWebProcessProxy.h; sourceTree = "<group>"; };
|
||||
1ADFF4AD24C8ED32006DC7AE /* ResourcePolicyManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ResourcePolicyManager.swift; sourceTree = "<group>"; };
|
||||
1ADFF4BF24CA6964006DC7AE /* URLBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URLBar.swift; sourceTree = "<group>"; };
|
||||
1ADFF4C224CA6AF6006DC7AE /* CGPoint+Utils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CGPoint+Utils.swift"; sourceTree = "<group>"; };
|
||||
1ADFF4C624CA6DEB006DC7AE /* UIEdgeInsets+Layout.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIEdgeInsets+Layout.swift"; sourceTree = "<group>"; };
|
||||
1ADFF4C824CA793E006DC7AE /* ToolbarViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ToolbarViewController.swift; sourceTree = "<group>"; };
|
||||
1ADFF4CA24CB8278006DC7AE /* ScriptControllerIconView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScriptControllerIconView.swift; sourceTree = "<group>"; };
|
||||
1ADFF4CC24CBB0C8006DC7AE /* ScriptPolicyViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScriptPolicyViewController.swift; sourceTree = "<group>"; };
|
||||
1ADFF4CF24CBBCD1006DC7AE /* ScriptPolicyControl.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScriptPolicyControl.swift; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
@@ -112,6 +126,8 @@
|
||||
1ADFF46124C7DE53006DC7AE /* SceneDelegate.swift */,
|
||||
1ADFF47A24C7E176006DC7AE /* Backend */,
|
||||
1ADFF47724C7DFE8006DC7AE /* Browser View */,
|
||||
1ADFF4CE24CBBCBD006DC7AE /* Script Policy UI */,
|
||||
1ADFF4C124CA6AE4006DC7AE /* Utilities */,
|
||||
1ADFF4AF24C92E2F006DC7AE /* Web Process Bundle Bridge */,
|
||||
1ADFF47624C7DF7F006DC7AE /* Supporting Files */,
|
||||
);
|
||||
@@ -135,6 +151,8 @@
|
||||
children = (
|
||||
1ADFF47324C7DE9C006DC7AE /* BrowserViewController.swift */,
|
||||
1ADFF47824C7DFF8006DC7AE /* BrowserView.swift */,
|
||||
1ADFF4C824CA793E006DC7AE /* ToolbarViewController.swift */,
|
||||
1ADFF4BF24CA6964006DC7AE /* URLBar.swift */,
|
||||
);
|
||||
path = "Browser View";
|
||||
sourceTree = "<group>";
|
||||
@@ -176,6 +194,25 @@
|
||||
path = "Web Process Bundle Bridge";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
1ADFF4C124CA6AE4006DC7AE /* Utilities */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
1ADFF4C224CA6AF6006DC7AE /* CGPoint+Utils.swift */,
|
||||
1ADFF4C624CA6DEB006DC7AE /* UIEdgeInsets+Layout.swift */,
|
||||
);
|
||||
path = Utilities;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
1ADFF4CE24CBBCBD006DC7AE /* Script Policy UI */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
1ADFF4CA24CB8278006DC7AE /* ScriptControllerIconView.swift */,
|
||||
1ADFF4CF24CBBCD1006DC7AE /* ScriptPolicyControl.swift */,
|
||||
1ADFF4CC24CBB0C8006DC7AE /* ScriptPolicyViewController.swift */,
|
||||
);
|
||||
path = "Script Policy UI";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* End PBXGroup section */
|
||||
|
||||
/* Begin PBXNativeTarget section */
|
||||
@@ -277,10 +314,17 @@
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
1ADFF46024C7DE53006DC7AE /* AppDelegate.swift in Sources */,
|
||||
1ADFF4C024CA6964006DC7AE /* URLBar.swift in Sources */,
|
||||
1ADFF4C724CA6DEB006DC7AE /* UIEdgeInsets+Layout.swift in Sources */,
|
||||
1ADFF4AE24C8ED32006DC7AE /* ResourcePolicyManager.swift in Sources */,
|
||||
1ADFF47424C7DE9C006DC7AE /* BrowserViewController.swift in Sources */,
|
||||
1ADFF4D024CBBCD1006DC7AE /* ScriptPolicyControl.swift in Sources */,
|
||||
1ADFF48D24C8C176006DC7AE /* SBRProcessBundleBridge.m in Sources */,
|
||||
1ADFF46224C7DE53006DC7AE /* SceneDelegate.swift in Sources */,
|
||||
1ADFF4CB24CB8278006DC7AE /* ScriptControllerIconView.swift in Sources */,
|
||||
1ADFF4C324CA6AF6006DC7AE /* CGPoint+Utils.swift in Sources */,
|
||||
1ADFF4C924CA793E006DC7AE /* ToolbarViewController.swift in Sources */,
|
||||
1ADFF4CD24CBB0C8006DC7AE /* ScriptPolicyViewController.swift in Sources */,
|
||||
1ADFF47924C7DFF8006DC7AE /* BrowserView.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
|
||||
@@ -26,6 +26,12 @@ class ResourcePolicyManager: NSObject, SBRResourceOriginPolicyDataSource
|
||||
func allowOriginToLoadScriptResources(_ origin: String)
|
||||
{
|
||||
allowedOriginSet.formUnion([ origin ])
|
||||
UserDefaults.standard.set(allowedOriginSet, forKey: Self.AllowedOriginsDefaultsKey)
|
||||
UserDefaults.standard.set(Array(allowedOriginSet), forKey: Self.AllowedOriginsDefaultsKey)
|
||||
}
|
||||
|
||||
func disallowOriginToLoadScriptResources(_ origin: String)
|
||||
{
|
||||
allowedOriginSet.remove(origin)
|
||||
UserDefaults.standard.set(Array(allowedOriginSet), forKey: Self.AllowedOriginsDefaultsKey)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,19 +5,59 @@
|
||||
// Created by James Magahern on 7/21/20.
|
||||
//
|
||||
|
||||
import Combine
|
||||
import UIKit
|
||||
import WebKit
|
||||
|
||||
class BrowserView: UIView
|
||||
{
|
||||
var toolbarView: ToolbarView? {
|
||||
didSet { addSubview(toolbarView!) }
|
||||
}
|
||||
|
||||
var webView: WKWebView? {
|
||||
didSet { addSubview(webView!); setNeedsLayout() }
|
||||
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
|
||||
|
||||
init()
|
||||
{
|
||||
scriptBlockerButtonItem = UIBarButtonItem(title: "0", image: nil, primaryAction: UIAction(handler: { action in
|
||||
// present
|
||||
}), menu: nil)
|
||||
override var canBecomeFirstResponder: Bool { true }
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
@@ -18,7 +18,6 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate {
|
||||
|
||||
navigationController.viewControllers = [ browserViewController ]
|
||||
navigationController.setNavigationBarHidden(true, animated: false)
|
||||
navigationController.setToolbarHidden(false, animated: false)
|
||||
|
||||
let window = UIWindow(windowScene: windowScene)
|
||||
window.rootViewController = navigationController
|
||||
|
||||
55
SBrowser/Script Policy UI/ScriptControllerIconView.swift
Normal file
55
SBrowser/Script Policy UI/ScriptControllerIconView.swift
Normal file
@@ -0,0 +1,55 @@
|
||||
//
|
||||
// ScriptControllerIconView.swift
|
||||
// SBrowser
|
||||
//
|
||||
// Created by James Magahern on 7/24/20.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
class ScriptControllerIconView: UIButton
|
||||
{
|
||||
private let labelView = UILabel(frame: .zero)
|
||||
|
||||
convenience init() {
|
||||
self.init(frame: .zero)
|
||||
|
||||
addSubview(labelView)
|
||||
|
||||
let image = UIImage(systemName: "shield")
|
||||
setImage(image, for: .normal)
|
||||
|
||||
imageView?.contentMode = .scaleAspectFit
|
||||
labelView.backgroundColor = .systemRed
|
||||
labelView.textAlignment = .center
|
||||
labelView.layer.cornerRadius = 4.0
|
||||
labelView.layer.masksToBounds = true
|
||||
labelView.font = .boldSystemFont(ofSize: 8)
|
||||
labelView.textColor = .white
|
||||
|
||||
setBlockedScriptsNumber(0)
|
||||
}
|
||||
|
||||
public func setBlockedScriptsNumber(_ num: Int) {
|
||||
if num > 0 {
|
||||
labelView.isHidden = false
|
||||
labelView.text = "\(num)"
|
||||
} else {
|
||||
labelView.isHidden = true
|
||||
}
|
||||
|
||||
setNeedsLayout()
|
||||
}
|
||||
|
||||
override func sizeThatFits(_ size: CGSize) -> CGSize {
|
||||
return CGSize(width: 44.0, height: 44.0)
|
||||
}
|
||||
|
||||
override func layoutSubviews() {
|
||||
super.layoutSubviews()
|
||||
|
||||
labelView.sizeToFit()
|
||||
labelView.center = CGPoint(x: bounds.center.x + 10, y: bounds.center.y + 10)
|
||||
labelView.bounds = labelView.bounds.insetBy(dx: -2.0, dy: -2.0)
|
||||
}
|
||||
}
|
||||
73
SBrowser/Script Policy UI/ScriptPolicyControl.swift
Normal file
73
SBrowser/Script Policy UI/ScriptPolicyControl.swift
Normal file
@@ -0,0 +1,73 @@
|
||||
//
|
||||
// ScriptPolicyControl.swift
|
||||
// SBrowser
|
||||
//
|
||||
// Created by James Magahern on 7/24/20.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
class ScriptPolicyControl: UIControl
|
||||
{
|
||||
enum PolicyStatus {
|
||||
case allowed
|
||||
case blocked
|
||||
}
|
||||
|
||||
var policyStatus: PolicyStatus = .blocked {
|
||||
didSet {
|
||||
sendActions(for: .valueChanged)
|
||||
setNeedsLayout()
|
||||
}
|
||||
}
|
||||
|
||||
private class PolicyButton: UIButton {
|
||||
override func imageRect(forContentRect contentRect: CGRect) -> CGRect {
|
||||
contentRect.insetBy(dx: 8.0, dy: 8.0)
|
||||
}
|
||||
}
|
||||
|
||||
private let allowButton = PolicyButton(frame: .zero)
|
||||
private let denyButton = PolicyButton(frame: .zero)
|
||||
|
||||
convenience init() {
|
||||
self.init(frame: .zero)
|
||||
|
||||
allowButton.addAction(UIAction(handler: { _ in
|
||||
self.policyStatus = .allowed
|
||||
}), for: .touchUpInside)
|
||||
allowButton.imageView?.contentMode = .scaleAspectFit
|
||||
addSubview(allowButton)
|
||||
|
||||
denyButton.addAction(UIAction(handler: { _ in
|
||||
self.policyStatus = .blocked
|
||||
}), for: .touchUpInside)
|
||||
denyButton.imageView?.contentMode = .scaleAspectFit
|
||||
addSubview(denyButton)
|
||||
}
|
||||
|
||||
override var intrinsicContentSize: CGSize {
|
||||
CGSize(width: 100.0, height: UIView.noIntrinsicMetric)
|
||||
}
|
||||
|
||||
override func layoutSubviews() {
|
||||
super.layoutSubviews()
|
||||
|
||||
allowButton.frame = CGRect(origin: .zero, size: CGSize(width: bounds.width / 2, height: bounds.height))
|
||||
denyButton.frame = CGRect(origin: CGPoint(x: allowButton.frame.maxX, y: 0), size: allowButton.frame.size)
|
||||
|
||||
if policyStatus == .allowed {
|
||||
allowButton.tintColor = .blue
|
||||
allowButton.setImage(UIImage(systemName: "play.circle.fill"), for: .normal)
|
||||
|
||||
denyButton.tintColor = .darkGray
|
||||
denyButton.setImage(UIImage(systemName: "stop.circle"), for: .normal)
|
||||
} else {
|
||||
allowButton.tintColor = .darkGray
|
||||
allowButton.setImage(UIImage(systemName: "play.circle"), for: .normal)
|
||||
|
||||
denyButton.tintColor = .red
|
||||
denyButton.setImage(UIImage(systemName: "stop.circle.fill"), for: .normal)
|
||||
}
|
||||
}
|
||||
}
|
||||
113
SBrowser/Script Policy UI/ScriptPolicyViewController.swift
Normal file
113
SBrowser/Script Policy UI/ScriptPolicyViewController.swift
Normal file
@@ -0,0 +1,113 @@
|
||||
//
|
||||
// ScriptPolicyViewController.swift
|
||||
// SBrowser
|
||||
//
|
||||
// Created by James Magahern on 7/24/20.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
protocol ScriptPolicyViewControllerDelegate {
|
||||
func didChangeScriptPolicy()
|
||||
}
|
||||
|
||||
class ScriptPolicyControlListCell: UICollectionViewListCell
|
||||
{
|
||||
let policyControl = ScriptPolicyControl()
|
||||
override init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
addSubview(policyControl)
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
override func layoutSubviews() {
|
||||
super.layoutSubviews()
|
||||
|
||||
let policyControlWidth = CGFloat(100.0)
|
||||
policyControl.frame = CGRect(x: bounds.maxX - policyControlWidth, y: 0, width: policyControlWidth, height: bounds.height)
|
||||
bringSubviewToFront(policyControl)
|
||||
|
||||
contentView.frame = CGRect(origin: contentView.frame.origin, size: CGSize(width: bounds.width - policyControl.frame.width, height: contentView.frame.height))
|
||||
}
|
||||
}
|
||||
|
||||
class ScriptPolicyViewController: UIViewController, UICollectionViewDelegate
|
||||
{
|
||||
var collectionView: UICollectionView?
|
||||
var delegate: ScriptPolicyViewControllerDelegate? = nil
|
||||
var dataSource: UICollectionViewDiffableDataSource<Int, String>?
|
||||
|
||||
private var didChangeScriptPolicy = false
|
||||
|
||||
convenience init(policyManager: ResourcePolicyManager, blockedScripts: Set<String>) {
|
||||
self.init(nibName: nil, bundle: nil)
|
||||
|
||||
let listConfig = UICollectionLayoutListConfiguration(appearance: .grouped)
|
||||
let listLayout = UICollectionViewCompositionalLayout.list(using: listConfig)
|
||||
let collectionView = UICollectionView(frame: .zero, collectionViewLayout: listLayout)
|
||||
|
||||
let registry = UICollectionView.CellRegistration<ScriptPolicyControlListCell, String> { (listCell, indexPath, item) in
|
||||
var config = listCell.defaultContentConfiguration()
|
||||
config.text = item
|
||||
|
||||
listCell.contentConfiguration = config
|
||||
|
||||
if policyManager.allowedOriginsForScriptResources().contains(item) {
|
||||
listCell.policyControl.policyStatus = .allowed
|
||||
} else {
|
||||
listCell.policyControl.policyStatus = .blocked
|
||||
}
|
||||
|
||||
listCell.policyControl.addAction(UIAction(handler: { _ in
|
||||
if listCell.policyControl.policyStatus == .allowed {
|
||||
policyManager.allowOriginToLoadScriptResources(item)
|
||||
} else {
|
||||
policyManager.disallowOriginToLoadScriptResources(item)
|
||||
}
|
||||
|
||||
self.didChangeScriptPolicy = true
|
||||
}), for: .valueChanged)
|
||||
}
|
||||
|
||||
let dataSource = UICollectionViewDiffableDataSource<Int, String>(collectionView: collectionView) { (collectionView, indexPath, item) -> UICollectionViewCell? in
|
||||
collectionView.dequeueConfiguredReusableCell(using: registry, for: indexPath, item: item)
|
||||
}
|
||||
|
||||
collectionView.dataSource = dataSource
|
||||
collectionView.delegate = self
|
||||
|
||||
var snapshot = dataSource.snapshot()
|
||||
snapshot.appendSections([ 0 ])
|
||||
snapshot.appendItems(Array(blockedScripts))
|
||||
dataSource.apply(snapshot)
|
||||
|
||||
self.dataSource = dataSource
|
||||
self.collectionView = collectionView
|
||||
|
||||
title = "Script Origin Policy"
|
||||
navigationItem.rightBarButtonItem = UIBarButtonItem(systemItem: .done, primaryAction: UIAction(handler: { action in
|
||||
if self.didChangeScriptPolicy {
|
||||
self.delegate?.didChangeScriptPolicy()
|
||||
}
|
||||
|
||||
self.dismiss(animated: true, completion: nil)
|
||||
}), menu: nil)
|
||||
}
|
||||
|
||||
override func loadView() {
|
||||
self.view = collectionView
|
||||
}
|
||||
|
||||
// MARK: UICollectionViewDelegate
|
||||
|
||||
func collectionView(_ collectionView: UICollectionView, shouldHighlightItemAt indexPath: IndexPath) -> Bool {
|
||||
false
|
||||
}
|
||||
|
||||
func collectionView(_ collectionView: UICollectionView, shouldSelectItemAt indexPath: IndexPath) -> Bool {
|
||||
false
|
||||
}
|
||||
}
|
||||
@@ -3,3 +3,6 @@
|
||||
//
|
||||
|
||||
#import "SBRProcessBundleBridge.h"
|
||||
|
||||
// SPI
|
||||
#import <UIKit/UITextField_Private.h>
|
||||
|
||||
17
SBrowser/Utilities/CGPoint+Utils.swift
Normal file
17
SBrowser/Utilities/CGPoint+Utils.swift
Normal file
@@ -0,0 +1,17 @@
|
||||
//
|
||||
// CGPoint+Utils.swift
|
||||
// SBrowser
|
||||
//
|
||||
// Created by James Magahern on 7/23/20.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
extension CGRect
|
||||
{
|
||||
var center: CGPoint {
|
||||
get {
|
||||
return CGPoint(x: size.width / 2.0, y: size.height / 2.0)
|
||||
}
|
||||
}
|
||||
}
|
||||
17
SBrowser/Utilities/UIEdgeInsets+Layout.swift
Normal file
17
SBrowser/Utilities/UIEdgeInsets+Layout.swift
Normal file
@@ -0,0 +1,17 @@
|
||||
//
|
||||
// UIEdgeInsets+Layout.swift
|
||||
// SBrowser
|
||||
//
|
||||
// Created by James Magahern on 7/23/20.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
extension UIEdgeInsets
|
||||
{
|
||||
var negative: UIEdgeInsets {
|
||||
get {
|
||||
return UIEdgeInsets(top: -top, left: -left, bottom: -bottom, right: -right)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -75,7 +75,7 @@
|
||||
|
||||
- (void)policyDataSourceDidChange
|
||||
{
|
||||
NSSet<NSString *> *allowedOrigins = [_policyDataSource allowedOriginsForScriptResources];
|
||||
NSArray<NSString *> *allowedOrigins = [[_policyDataSource allowedOriginsForScriptResources] allObjects];
|
||||
[_webProcessProxy syncAllowedResourceOrigins:allowedOrigins];
|
||||
}
|
||||
|
||||
|
||||
@@ -36,13 +36,9 @@
|
||||
NSLog(@"SBRProcessPlugin: Helloooooo");
|
||||
}
|
||||
|
||||
- (void)syncAllowedResourceOrigins:(NSSet<NSString *> *)allowedOrigins
|
||||
- (void)syncAllowedResourceOrigins:(NSArray<NSString *> *)allowedOrigins
|
||||
{
|
||||
if (!_allowedResourceOrigins) {
|
||||
_allowedResourceOrigins = [allowedOrigins mutableCopy];
|
||||
} else {
|
||||
[_allowedResourceOrigins unionSet:allowedOrigins];
|
||||
}
|
||||
_allowedResourceOrigins = [NSMutableSet setWithArray:allowedOrigins];
|
||||
}
|
||||
|
||||
#pragma mark <WKWebProcessPlugIn>
|
||||
|
||||
@@ -10,6 +10,6 @@
|
||||
@protocol SBRWebProcessProxy <NSObject>
|
||||
|
||||
- (void)hello;
|
||||
- (void)syncAllowedResourceOrigins:(NSSet<NSString *> *)allowedOrigins;
|
||||
- (void)syncAllowedResourceOrigins:(NSArray<NSString *> *)allowedOrigins;
|
||||
|
||||
@end
|
||||
|
||||
Reference in New Issue
Block a user