Script origins control inline policy, all scripts allowed for tab
This commit is contained in:
@@ -10,28 +10,31 @@ import Foundation
|
|||||||
class ResourcePolicyManager: NSObject, SBRResourceOriginPolicyDataSource
|
class ResourcePolicyManager: NSObject, SBRResourceOriginPolicyDataSource
|
||||||
{
|
{
|
||||||
static let AllowedOriginsDefaultsKey = "allowedOrigins"
|
static let AllowedOriginsDefaultsKey = "allowedOrigins"
|
||||||
private var allowedOriginSet: Set<String> = {
|
static let EnabledOriginsDefaultsKey = "enabledOrigins"
|
||||||
if let allowedOrigins = UserDefaults.standard.array(forKey: AllowedOriginsDefaultsKey) as? [String] {
|
|
||||||
return Set<String>(allowedOrigins)
|
private static func stringSetForKey(_ key: String) -> Set<String> {
|
||||||
|
if let set = UserDefaults.standard.array(forKey: key) as? [String] {
|
||||||
|
return Set<String>(set)
|
||||||
}
|
}
|
||||||
|
|
||||||
return Set<String>()
|
return Set<String>()
|
||||||
}()
|
|
||||||
|
|
||||||
func allowedOriginsForScriptResources() -> Set<String>
|
|
||||||
{
|
|
||||||
allowedOriginSet
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func allowOriginToLoadScriptResources(_ origin: String)
|
private static func saveStringSet(_ set: Set<String>, forKey key: String) {
|
||||||
{
|
UserDefaults.standard.set(Array(set), forKey: key)
|
||||||
|
}
|
||||||
|
|
||||||
|
private var allowedOriginSet: Set<String> = stringSetForKey(AllowedOriginsDefaultsKey) {
|
||||||
|
didSet { Self.saveStringSet(allowedOriginSet, forKey: Self.AllowedOriginsDefaultsKey) }
|
||||||
|
}
|
||||||
|
|
||||||
|
func allowedOriginsForScriptResources() -> Set<String> { allowedOriginSet }
|
||||||
|
|
||||||
|
func allowOriginToLoadScriptResources(_ origin: String) {
|
||||||
allowedOriginSet.formUnion([ origin ])
|
allowedOriginSet.formUnion([ origin ])
|
||||||
UserDefaults.standard.set(Array(allowedOriginSet), forKey: Self.AllowedOriginsDefaultsKey)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func disallowOriginToLoadScriptResources(_ origin: String)
|
func disallowOriginToLoadScriptResources(_ origin: String) {
|
||||||
{
|
|
||||||
allowedOriginSet.remove(origin)
|
allowedOriginSet.remove(origin)
|
||||||
UserDefaults.standard.set(Array(allowedOriginSet), forKey: Self.AllowedOriginsDefaultsKey)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,31 +37,48 @@ class BrowserView: UIView
|
|||||||
addSubview(titlebarView)
|
addSubview(titlebarView)
|
||||||
|
|
||||||
keyboardWillShowObserver = NotificationCenter.default.publisher(for: UIWindow.keyboardWillShowNotification).sink { notification in
|
keyboardWillShowObserver = NotificationCenter.default.publisher(for: UIWindow.keyboardWillShowNotification).sink { notification in
|
||||||
if let keyboardFrame = notification.userInfo?[UIWindow.keyboardFrameEndUserInfoKey] as? CGRect {
|
self.adjustOffsetForKeyboardNotification(userInfo: notification.userInfo!)
|
||||||
self.keyboardLayoutOffset = self.bounds.height - keyboardFrame.minY
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
keyboardWillHideObserver = NotificationCenter.default.publisher(for: UIWindow.keyboardWillHideNotification).sink { notification in
|
keyboardWillHideObserver = NotificationCenter.default.publisher(for: UIWindow.keyboardWillHideNotification).sink { notification in
|
||||||
if let keyboardFrame = notification.userInfo?[UIWindow.keyboardFrameEndUserInfoKey] as? CGRect {
|
self.adjustOffsetForKeyboardNotification(userInfo: notification.userInfo!)
|
||||||
self.keyboardLayoutOffset = self.bounds.height - keyboardFrame.minY
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override func layoutSubviews()
|
private func adjustOffsetForKeyboardNotification(userInfo: [AnyHashable : Any]) {
|
||||||
{
|
guard let keyboardEndFrame = userInfo[UIWindow.keyboardFrameEndUserInfoKey] as? CGRect else { return }
|
||||||
|
guard let animationDuration = userInfo[UIWindow.keyboardAnimationDurationUserInfoKey] as? TimeInterval else { return }
|
||||||
|
guard let animationCurve = userInfo[UIWindow.keyboardAnimationCurveUserInfoKey] as? Int else { return }
|
||||||
|
|
||||||
|
let animationOptions: UIView.AnimationOptions = { curve -> UIView.AnimationOptions in
|
||||||
|
switch UIView.AnimationCurve(rawValue: curve) {
|
||||||
|
case .easeIn: return .curveEaseIn
|
||||||
|
case .easeOut: return .curveEaseOut
|
||||||
|
case .easeInOut: return .curveEaseInOut
|
||||||
|
default: return .init()
|
||||||
|
}
|
||||||
|
}(animationCurve)
|
||||||
|
|
||||||
|
self.keyboardLayoutOffset = bounds.height - keyboardEndFrame.minY
|
||||||
|
UIView.animate(withDuration: animationDuration, delay: 0.0, options: animationOptions, animations: { self.layoutIfNeeded() }, completion: nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
override func layoutSubviews() {
|
||||||
super.layoutSubviews()
|
super.layoutSubviews()
|
||||||
webView?.frame = bounds
|
webView?.frame = bounds
|
||||||
|
|
||||||
if let toolbarView = toolbarView {
|
if let toolbarView = toolbarView {
|
||||||
var toolbarSize = toolbarView.sizeThatFits(bounds.size)
|
var toolbarSize = toolbarView.sizeThatFits(bounds.size)
|
||||||
|
|
||||||
|
var bottomOffset: CGFloat = 0.0
|
||||||
if keyboardLayoutOffset == 0 {
|
if keyboardLayoutOffset == 0 {
|
||||||
toolbarSize.height += safeAreaInsets.bottom
|
toolbarSize.height += safeAreaInsets.bottom
|
||||||
|
} else if toolbarView.urlBar?.textField.isFirstResponder ?? false {
|
||||||
|
bottomOffset = keyboardLayoutOffset
|
||||||
}
|
}
|
||||||
|
|
||||||
toolbarView.bounds = CGRect(origin: .zero, size: toolbarSize)
|
toolbarView.bounds = CGRect(origin: .zero, size: toolbarSize)
|
||||||
toolbarView.center = CGPoint(x: bounds.center.x, y: bounds.maxY - (toolbarView.bounds.height / 2) - keyboardLayoutOffset)
|
toolbarView.center = CGPoint(x: bounds.center.x, y: bounds.maxY - (toolbarView.bounds.height / 2) - bottomOffset)
|
||||||
}
|
}
|
||||||
|
|
||||||
bringSubviewToFront(titlebarView)
|
bringSubviewToFront(titlebarView)
|
||||||
@@ -77,7 +94,7 @@ class BrowserView: UIView
|
|||||||
var webViewContentInset = UIEdgeInsets()
|
var webViewContentInset = UIEdgeInsets()
|
||||||
webViewContentInset.top = titlebarView.frame.height
|
webViewContentInset.top = titlebarView.frame.height
|
||||||
webViewContentInset.bottom = toolbarView?.frame.height ?? 0
|
webViewContentInset.bottom = toolbarView?.frame.height ?? 0
|
||||||
webView?.scrollView.contentInset = webViewContentInset
|
webView?.scrollView.contentInset = webViewContentInset.subtracting(safeAreaInsets)
|
||||||
webView?.scrollView.scrollIndicatorInsets = webViewContentInset
|
webView?.scrollView.scrollIndicatorInsets = webViewContentInset
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,8 +14,11 @@ class BrowserViewController: UIViewController,
|
|||||||
let bridge = SBRProcessBundleBridge()
|
let bridge = SBRProcessBundleBridge()
|
||||||
let browserView = BrowserView()
|
let browserView = BrowserView()
|
||||||
|
|
||||||
|
var javaScriptEnabledForTab: Bool = false
|
||||||
|
|
||||||
private let policyManager = ResourcePolicyManager()
|
private let policyManager = ResourcePolicyManager()
|
||||||
private let toolbarController = ToolbarViewController()
|
private let toolbarController = ToolbarViewController()
|
||||||
|
private var allowedScriptOrigins = Set<String>()
|
||||||
private var blockedScriptOrigins = Set<String>()
|
private var blockedScriptOrigins = Set<String>()
|
||||||
override var canBecomeFirstResponder: Bool { true }
|
override var canBecomeFirstResponder: Bool { true }
|
||||||
|
|
||||||
@@ -52,8 +55,11 @@ class BrowserViewController: UIViewController,
|
|||||||
|
|
||||||
// Script button
|
// Script button
|
||||||
toolbarController.scriptControllerIconView.addAction(UIAction(handler: { action in
|
toolbarController.scriptControllerIconView.addAction(UIAction(handler: { action in
|
||||||
let scriptViewController = ScriptPolicyViewController(policyManager: self.policyManager, blockedScripts: self.blockedScriptOrigins)
|
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.delegate = self
|
||||||
|
scriptViewController.allowScriptsForTab = self.javaScriptEnabledForTab
|
||||||
|
|
||||||
let navController = UINavigationController(rootViewController: scriptViewController)
|
let navController = UINavigationController(rootViewController: scriptViewController)
|
||||||
self.present(navController, animated: true, completion: nil)
|
self.present(navController, animated: true, completion: nil)
|
||||||
@@ -64,7 +70,11 @@ class BrowserViewController: UIViewController,
|
|||||||
|
|
||||||
// Load progress
|
// Load progress
|
||||||
loadingObservation = webView.observe(\.estimatedProgress) { (webView, observedChange) in
|
loadingObservation = webView.observe(\.estimatedProgress) { (webView, observedChange) in
|
||||||
self.toolbarController.urlBar.loadProgress = .loading(progress: webView.estimatedProgress)
|
if webView.estimatedProgress == 1.0 {
|
||||||
|
self.toolbarController.urlBar.loadProgress = .complete
|
||||||
|
} else {
|
||||||
|
self.toolbarController.urlBar.loadProgress = .loading(progress: webView.estimatedProgress)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Title observer
|
// Title observer
|
||||||
@@ -76,7 +86,7 @@ class BrowserViewController: UIViewController,
|
|||||||
}
|
}
|
||||||
|
|
||||||
override func viewDidLoad() {
|
override func viewDidLoad() {
|
||||||
beginLoadingURL(URL(string: "https://reddit.com")!)
|
beginLoadingURL(URL(string: "https://google.com")!)
|
||||||
}
|
}
|
||||||
|
|
||||||
override func viewWillAppear(_ animated: Bool) {
|
override func viewWillAppear(_ animated: Bool) {
|
||||||
@@ -95,9 +105,14 @@ class BrowserViewController: UIViewController,
|
|||||||
|
|
||||||
// MARK: SBRProcessBundleBridgeDelegate
|
// 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) {
|
func webProcess(_ bridge: SBRProcessBundleBridge, didBlockScriptResourceFromOrigin origin: String) {
|
||||||
print("Blocked script resource from origin: \(origin)")
|
print("Blocked script resource from origin: \(origin)")
|
||||||
|
|
||||||
blockedScriptOrigins.formUnion([ origin ])
|
blockedScriptOrigins.formUnion([ origin ])
|
||||||
updateScriptBlockerButton()
|
updateScriptBlockerButton()
|
||||||
}
|
}
|
||||||
@@ -117,6 +132,18 @@ class BrowserViewController: UIViewController,
|
|||||||
toolbarController.urlBar.loadProgress = .complete
|
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
|
// MARK: UITextField Delegate
|
||||||
|
|
||||||
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
|
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
|
||||||
@@ -140,4 +167,9 @@ class BrowserViewController: UIViewController,
|
|||||||
bridge.webView.reload()
|
bridge.webView.reload()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func setScriptsEnabledForTab(_ enabled: Bool) {
|
||||||
|
javaScriptEnabledForTab = enabled
|
||||||
|
bridge.allowAllScripts = enabled
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import UIKit
|
|||||||
|
|
||||||
class ToolbarButtonView: UIView
|
class ToolbarButtonView: UIView
|
||||||
{
|
{
|
||||||
private var buttonPadding = CGFloat(8.0)
|
private var buttonPadding = CGFloat(24.0)
|
||||||
private var buttonViews: [UIView] = []
|
private var buttonViews: [UIView] = []
|
||||||
|
|
||||||
func addButtonView(_ button: UIView) {
|
func addButtonView(_ button: UIView) {
|
||||||
@@ -28,7 +28,7 @@ class ToolbarButtonView: UIView
|
|||||||
|
|
||||||
override func layoutSubviews() {
|
override func layoutSubviews() {
|
||||||
var buttonRect = CGRect(origin: .zero, size: CGSize(width: 0, height: bounds.height))
|
var buttonRect = CGRect(origin: .zero, size: CGSize(width: 0, height: bounds.height))
|
||||||
buttonRect.origin.x = buttonPadding
|
buttonRect.origin.x = layoutMargins.left
|
||||||
|
|
||||||
for button in buttonViews {
|
for button in buttonViews {
|
||||||
let buttonSize = button.sizeThatFits(bounds.size)
|
let buttonSize = button.sizeThatFits(bounds.size)
|
||||||
@@ -44,9 +44,12 @@ class ToolbarView: UIView
|
|||||||
{
|
{
|
||||||
var urlBar: URLBar? { didSet { containerView.addSubview(urlBar!) } }
|
var urlBar: URLBar? { didSet { containerView.addSubview(urlBar!) } }
|
||||||
|
|
||||||
|
var cancelButtonVisible: Bool = false { didSet { layoutSubviews() } }
|
||||||
|
|
||||||
let containerView = UIView(frame: .zero)
|
let containerView = UIView(frame: .zero)
|
||||||
let backgroundView = UIVisualEffectView(effect: UIBlurEffect(style: .systemThickMaterial))
|
let backgroundView = UIVisualEffectView(effect: UIBlurEffect(style: .systemThickMaterial))
|
||||||
let buttonsView = ToolbarButtonView(frame: .zero)
|
let buttonsView = ToolbarButtonView(frame: .zero)
|
||||||
|
let cancelButton = UIButton(type: .system)
|
||||||
|
|
||||||
convenience init()
|
convenience init()
|
||||||
{
|
{
|
||||||
@@ -55,6 +58,9 @@ class ToolbarView: UIView
|
|||||||
addSubview(containerView)
|
addSubview(containerView)
|
||||||
|
|
||||||
containerView.addSubview(buttonsView)
|
containerView.addSubview(buttonsView)
|
||||||
|
|
||||||
|
cancelButton.setTitle("Cancel", for: .normal)
|
||||||
|
containerView.addSubview(cancelButton)
|
||||||
}
|
}
|
||||||
|
|
||||||
override func sizeThatFits(_ size: CGSize) -> CGSize
|
override func sizeThatFits(_ size: CGSize) -> CGSize
|
||||||
@@ -73,12 +79,32 @@ class ToolbarView: UIView
|
|||||||
containerView.frame = containerBounds
|
containerView.frame = containerBounds
|
||||||
containerView.frame = containerView.frame.insetBy(dx: 8.0, dy: 4.0)
|
containerView.frame = containerView.frame.insetBy(dx: 8.0, dy: 4.0)
|
||||||
|
|
||||||
|
// Cancel button
|
||||||
|
let urlBarPadding: CGFloat = 8.0
|
||||||
|
let cancelButtonSize = cancelButton.sizeThatFits(containerView.bounds.size)
|
||||||
|
cancelButton.frame = CGRect(origin: CGPoint(x: (urlBar?.frame.maxX ?? 0) + urlBarPadding, y: 0),
|
||||||
|
size: CGSize(width: cancelButtonSize.width, height: containerView.bounds.height))
|
||||||
|
|
||||||
|
// Toolbar buttons
|
||||||
let toolbarSize = buttonsView.sizeThatFits(containerView.bounds.size)
|
let toolbarSize = buttonsView.sizeThatFits(containerView.bounds.size)
|
||||||
if let urlBar = urlBar {
|
buttonsView.frame = CGRect(origin: CGPoint(x: (urlBar?.frame.maxX ?? 0) + urlBarPadding, y: 0), size: toolbarSize)
|
||||||
urlBar.frame = CGRect(origin: .zero, size: CGSize(width: containerView.bounds.width - toolbarSize.width, height: toolbarSize.height))
|
|
||||||
|
var avoidingSize: CGSize = .zero
|
||||||
|
if cancelButtonVisible {
|
||||||
|
cancelButton.alpha = 1.0
|
||||||
|
buttonsView.alpha = 0.0
|
||||||
|
|
||||||
|
avoidingSize = cancelButtonSize
|
||||||
|
} else {
|
||||||
|
cancelButton.alpha = 0.0
|
||||||
|
buttonsView.alpha = 1.0
|
||||||
|
|
||||||
|
avoidingSize = toolbarSize
|
||||||
}
|
}
|
||||||
|
|
||||||
buttonsView.frame = CGRect(origin: CGPoint(x: urlBar?.frame.maxX ?? 0 + 8.0, y: 0), size: toolbarSize)
|
if let urlBar = urlBar {
|
||||||
|
urlBar.frame = CGRect(origin: .zero, size: CGSize(width: containerView.bounds.width - avoidingSize.width - urlBarPadding, height: containerView.bounds.height))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -88,15 +114,30 @@ class ToolbarViewController: UIViewController
|
|||||||
let toolbarView = ToolbarView()
|
let toolbarView = ToolbarView()
|
||||||
let scriptControllerIconView = ScriptControllerIconView()
|
let scriptControllerIconView = ScriptControllerIconView()
|
||||||
let shareButton = UIButton(frame: .zero)
|
let shareButton = UIButton(frame: .zero)
|
||||||
|
let darkModeButton = UIButton(frame: .zero)
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
super.init(nibName: nil, bundle: nil)
|
super.init(nibName: nil, bundle: nil)
|
||||||
|
|
||||||
toolbarView.urlBar = urlBar
|
toolbarView.urlBar = urlBar
|
||||||
|
|
||||||
|
darkModeButton.setImage(UIImage(systemName: "moon.circle"), for: .normal)
|
||||||
|
toolbarView.buttonsView.addButtonView(darkModeButton)
|
||||||
|
|
||||||
shareButton.setImage(UIImage(systemName: "square.and.arrow.up"), for: .normal)
|
shareButton.setImage(UIImage(systemName: "square.and.arrow.up"), for: .normal)
|
||||||
toolbarView.buttonsView.addButtonView(shareButton)
|
toolbarView.buttonsView.addButtonView(shareButton)
|
||||||
toolbarView.buttonsView.addButtonView(scriptControllerIconView)
|
toolbarView.buttonsView.addButtonView(scriptControllerIconView)
|
||||||
|
|
||||||
|
let toolbarAnimationDuration: TimeInterval = 0.3
|
||||||
|
urlBar.textField.addAction(.init(handler: { _ in
|
||||||
|
UIView.animate(withDuration: toolbarAnimationDuration) {
|
||||||
|
self.toolbarView.cancelButtonVisible = self.urlBar.textField.isFirstResponder
|
||||||
|
}
|
||||||
|
}), for: [ .editingDidBegin, .editingDidEnd ])
|
||||||
|
|
||||||
|
toolbarView.cancelButton.addAction(.init(handler: { action in
|
||||||
|
self.urlBar.textField.resignFirstResponder()
|
||||||
|
}), for: .touchUpInside)
|
||||||
}
|
}
|
||||||
|
|
||||||
override func loadView() {
|
override func loadView() {
|
||||||
|
|||||||
@@ -48,8 +48,13 @@ class URLBar: UIView
|
|||||||
textField.autocapitalizationType = .none
|
textField.autocapitalizationType = .none
|
||||||
textField.font = .systemFont(ofSize: 14.0)
|
textField.font = .systemFont(ofSize: 14.0)
|
||||||
textField.clearingBehavior = .clearOnInsertionAndShowSelectionTint
|
textField.clearingBehavior = .clearOnInsertionAndShowSelectionTint
|
||||||
|
textField.clearButtonMode = .whileEditing
|
||||||
addSubview(textField)
|
addSubview(textField)
|
||||||
|
|
||||||
|
textField.addAction(.init(handler: { _ in
|
||||||
|
self.refreshButton.isHidden = self.textField.isFirstResponder
|
||||||
|
}), for: [ .editingDidBegin, .editingDidEnd ])
|
||||||
|
|
||||||
refreshButton.tintColor = .secondaryLabel
|
refreshButton.tintColor = .secondaryLabel
|
||||||
refreshButton.setImage(refreshImage, for: .normal)
|
refreshButton.setImage(refreshImage, for: .normal)
|
||||||
addSubview(refreshButton)
|
addSubview(refreshButton)
|
||||||
|
|||||||
@@ -41,10 +41,6 @@ class ScriptControllerIconView: UIButton
|
|||||||
setNeedsLayout()
|
setNeedsLayout()
|
||||||
}
|
}
|
||||||
|
|
||||||
override func sizeThatFits(_ size: CGSize) -> CGSize {
|
|
||||||
return CGSize(width: 44.0, height: 44.0)
|
|
||||||
}
|
|
||||||
|
|
||||||
override func layoutSubviews() {
|
override func layoutSubviews() {
|
||||||
super.layoutSubviews()
|
super.layoutSubviews()
|
||||||
|
|
||||||
|
|||||||
@@ -15,10 +15,7 @@ class ScriptPolicyControl: UIControl
|
|||||||
}
|
}
|
||||||
|
|
||||||
var policyStatus: PolicyStatus = .blocked {
|
var policyStatus: PolicyStatus = .blocked {
|
||||||
didSet {
|
didSet { setNeedsLayout() }
|
||||||
sendActions(for: .valueChanged)
|
|
||||||
setNeedsLayout()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private class PolicyButton: UIButton {
|
private class PolicyButton: UIButton {
|
||||||
@@ -35,12 +32,14 @@ class ScriptPolicyControl: UIControl
|
|||||||
|
|
||||||
allowButton.addAction(UIAction(handler: { _ in
|
allowButton.addAction(UIAction(handler: { _ in
|
||||||
self.policyStatus = .allowed
|
self.policyStatus = .allowed
|
||||||
|
self.sendActions(for: .valueChanged)
|
||||||
}), for: .touchUpInside)
|
}), for: .touchUpInside)
|
||||||
allowButton.imageView?.contentMode = .scaleAspectFit
|
allowButton.imageView?.contentMode = .scaleAspectFit
|
||||||
addSubview(allowButton)
|
addSubview(allowButton)
|
||||||
|
|
||||||
denyButton.addAction(UIAction(handler: { _ in
|
denyButton.addAction(UIAction(handler: { _ in
|
||||||
self.policyStatus = .blocked
|
self.policyStatus = .blocked
|
||||||
|
self.sendActions(for: .valueChanged)
|
||||||
}), for: .touchUpInside)
|
}), for: .touchUpInside)
|
||||||
denyButton.imageView?.contentMode = .scaleAspectFit
|
denyButton.imageView?.contentMode = .scaleAspectFit
|
||||||
addSubview(denyButton)
|
addSubview(denyButton)
|
||||||
|
|||||||
@@ -9,10 +9,18 @@ import UIKit
|
|||||||
|
|
||||||
protocol ScriptPolicyViewControllerDelegate {
|
protocol ScriptPolicyViewControllerDelegate {
|
||||||
func didChangeScriptPolicy()
|
func didChangeScriptPolicy()
|
||||||
|
func setScriptsEnabledForTab(_ enabled: Bool)
|
||||||
}
|
}
|
||||||
|
|
||||||
class ScriptPolicyControlListCell: UICollectionViewListCell
|
class ScriptPolicyControlListCell: UICollectionViewListCell
|
||||||
{
|
{
|
||||||
|
var enabled: Bool = true {
|
||||||
|
didSet {
|
||||||
|
if enabled != oldValue {
|
||||||
|
setNeedsLayout()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
let policyControl = ScriptPolicyControl()
|
let policyControl = ScriptPolicyControl()
|
||||||
override init(frame: CGRect) {
|
override init(frame: CGRect) {
|
||||||
super.init(frame: frame)
|
super.init(frame: frame)
|
||||||
@@ -23,14 +31,46 @@ class ScriptPolicyControlListCell: UICollectionViewListCell
|
|||||||
fatalError("init(coder:) has not been implemented")
|
fatalError("init(coder:) has not been implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
override func layoutSubviews() {
|
||||||
|
let policyControlWidth = CGFloat(80.0)
|
||||||
|
policyControl.frame = CGRect(x: bounds.maxX - policyControlWidth - layoutMargins.right, y: 0, width: policyControlWidth, height: bounds.height)
|
||||||
|
bringSubviewToFront(policyControl)
|
||||||
|
|
||||||
|
super.layoutSubviews()
|
||||||
|
|
||||||
|
if enabled {
|
||||||
|
contentView.alpha = 1.0
|
||||||
|
policyControl.alpha = 1.0
|
||||||
|
policyControl.isUserInteractionEnabled = true
|
||||||
|
} else {
|
||||||
|
contentView.alpha = 0.5
|
||||||
|
policyControl.alpha = 0.5
|
||||||
|
policyControl.isUserInteractionEnabled = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class SwitchListCell: UICollectionViewListCell
|
||||||
|
{
|
||||||
|
let switchView = UISwitch(frame: .zero)
|
||||||
|
|
||||||
|
override init(frame: CGRect) {
|
||||||
|
super.init(frame: frame)
|
||||||
|
addSubview(switchView)
|
||||||
|
}
|
||||||
|
|
||||||
|
required init?(coder: NSCoder) {
|
||||||
|
fatalError("init(coder:) has not been implemented")
|
||||||
|
}
|
||||||
|
|
||||||
override func layoutSubviews() {
|
override func layoutSubviews() {
|
||||||
super.layoutSubviews()
|
super.layoutSubviews()
|
||||||
|
|
||||||
let policyControlWidth = CGFloat(100.0)
|
let switchWidth: CGFloat = switchView.sizeThatFits(bounds.size).width
|
||||||
policyControl.frame = CGRect(x: bounds.maxX - policyControlWidth, y: 0, width: policyControlWidth, height: bounds.height)
|
switchView.frame = CGRect(x: bounds.maxX - switchWidth - layoutMargins.right, y: 0, width: switchWidth, height: bounds.height)
|
||||||
bringSubviewToFront(policyControl)
|
switchView.frame = switchView.frame.centeredY(inRect: bounds)
|
||||||
|
contentView.frame = CGRect(origin: contentView.frame.origin, size: CGSize(width: switchView.frame.minX, height: contentView.frame.height))
|
||||||
contentView.frame = CGRect(origin: contentView.frame.origin, size: CGSize(width: bounds.width - policyControl.frame.width, height: contentView.frame.height))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -38,18 +78,58 @@ class ScriptPolicyViewController: UIViewController, UICollectionViewDelegate
|
|||||||
{
|
{
|
||||||
var collectionView: UICollectionView?
|
var collectionView: UICollectionView?
|
||||||
var delegate: ScriptPolicyViewControllerDelegate? = nil
|
var delegate: ScriptPolicyViewControllerDelegate? = nil
|
||||||
var dataSource: UICollectionViewDiffableDataSource<Int, String>?
|
var allowScriptsForTab = false
|
||||||
|
|
||||||
|
private var dataSource: UICollectionViewDiffableDataSource<Section, String>?
|
||||||
private var didChangeScriptPolicy = false
|
private var didChangeScriptPolicy = false
|
||||||
|
|
||||||
convenience init(policyManager: ResourcePolicyManager, blockedScripts: Set<String>) {
|
private enum Section: Int {
|
||||||
|
case tabOptions
|
||||||
|
case origins
|
||||||
|
}
|
||||||
|
|
||||||
|
private static let enableScriptsForTabItem: String = "enableScriptsForTab"
|
||||||
|
|
||||||
|
convenience init(policyManager: ResourcePolicyManager, hostOrigin: String, loadedScripts: Set<String>) {
|
||||||
self.init(nibName: nil, bundle: nil)
|
self.init(nibName: nil, bundle: nil)
|
||||||
|
|
||||||
let listConfig = UICollectionLayoutListConfiguration(appearance: .grouped)
|
let listConfig = UICollectionLayoutListConfiguration(appearance: .grouped)
|
||||||
let listLayout = UICollectionViewCompositionalLayout.list(using: listConfig)
|
let listLayout = UICollectionViewCompositionalLayout.list(using: listConfig)
|
||||||
let collectionView = UICollectionView(frame: .zero, collectionViewLayout: listLayout)
|
let collectionView = UICollectionView(frame: .zero, collectionViewLayout: listLayout)
|
||||||
|
|
||||||
let registry = UICollectionView.CellRegistration<ScriptPolicyControlListCell, String> { (listCell, indexPath, item) in
|
// Make sure host origin goes first in the list.
|
||||||
|
let otherOriginScripts = loadedScripts.subtracting([ hostOrigin ])
|
||||||
|
let originItems = [ hostOrigin ] + otherOriginScripts
|
||||||
|
|
||||||
|
let switchCellRegistry = UICollectionView.CellRegistration<SwitchListCell, String> { (listCell, indexPath, item) in
|
||||||
|
var config = listCell.defaultContentConfiguration()
|
||||||
|
if item == Self.enableScriptsForTabItem {
|
||||||
|
config.text = "Allow for Tab"
|
||||||
|
listCell.switchView.isOn = self.allowScriptsForTab
|
||||||
|
listCell.switchView.addAction(.init(handler: { _ in
|
||||||
|
let enabled = listCell.switchView.isOn
|
||||||
|
|
||||||
|
self.allowScriptsForTab = enabled
|
||||||
|
self.didChangeScriptPolicy = true
|
||||||
|
|
||||||
|
if var snapshot = self.dataSource?.snapshot() {
|
||||||
|
if enabled {
|
||||||
|
// Hide script origins
|
||||||
|
snapshot.deleteSections([ .origins ])
|
||||||
|
} else {
|
||||||
|
snapshot.appendSections([ .origins ])
|
||||||
|
snapshot.appendItems(originItems, toSection: .origins)
|
||||||
|
}
|
||||||
|
|
||||||
|
self.dataSource?.apply(snapshot, animatingDifferences: true)
|
||||||
|
}
|
||||||
|
}), for: .valueChanged)
|
||||||
|
}
|
||||||
|
|
||||||
|
listCell.contentConfiguration = config
|
||||||
|
}
|
||||||
|
|
||||||
|
let scriptPolicyRegistry = UICollectionView.CellRegistration<ScriptPolicyControlListCell, String> { (listCell, indexPath, item) in
|
||||||
var config = listCell.defaultContentConfiguration()
|
var config = listCell.defaultContentConfiguration()
|
||||||
config.text = item
|
config.text = item
|
||||||
|
|
||||||
@@ -62,26 +142,44 @@ class ScriptPolicyViewController: UIViewController, UICollectionViewDelegate
|
|||||||
}
|
}
|
||||||
|
|
||||||
listCell.policyControl.addAction(UIAction(handler: { _ in
|
listCell.policyControl.addAction(UIAction(handler: { _ in
|
||||||
if listCell.policyControl.policyStatus == .allowed {
|
let allowed: Bool = listCell.policyControl.policyStatus == .allowed
|
||||||
|
|
||||||
|
if allowed {
|
||||||
policyManager.allowOriginToLoadScriptResources(item)
|
policyManager.allowOriginToLoadScriptResources(item)
|
||||||
} else {
|
} else {
|
||||||
policyManager.disallowOriginToLoadScriptResources(item)
|
policyManager.disallowOriginToLoadScriptResources(item)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if item == hostOrigin {
|
||||||
|
if var snapshot = self.dataSource?.snapshot() {
|
||||||
|
snapshot.reloadItems(Array(otherOriginScripts))
|
||||||
|
self.dataSource?.apply(snapshot, animatingDifferences: true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
self.didChangeScriptPolicy = true
|
self.didChangeScriptPolicy = true
|
||||||
}), for: .valueChanged)
|
}), for: .valueChanged)
|
||||||
|
|
||||||
|
if item != hostOrigin {
|
||||||
|
listCell.enabled = policyManager.allowedOriginsForScriptResources().contains(hostOrigin)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let dataSource = UICollectionViewDiffableDataSource<Int, String>(collectionView: collectionView) { (collectionView, indexPath, item) -> UICollectionViewCell? in
|
let dataSource = UICollectionViewDiffableDataSource<Section, String>(collectionView: collectionView) { (collectionView, indexPath, item) -> UICollectionViewCell? in
|
||||||
collectionView.dequeueConfiguredReusableCell(using: registry, for: indexPath, item: item)
|
if item == Self.enableScriptsForTabItem {
|
||||||
|
return collectionView.dequeueConfiguredReusableCell(using: switchCellRegistry, for: indexPath, item: item)
|
||||||
|
}
|
||||||
|
|
||||||
|
return collectionView.dequeueConfiguredReusableCell(using: scriptPolicyRegistry, for: indexPath, item: item)
|
||||||
}
|
}
|
||||||
|
|
||||||
collectionView.dataSource = dataSource
|
collectionView.dataSource = dataSource
|
||||||
collectionView.delegate = self
|
collectionView.delegate = self
|
||||||
|
|
||||||
var snapshot = dataSource.snapshot()
|
var snapshot = dataSource.snapshot()
|
||||||
snapshot.appendSections([ 0 ])
|
snapshot.appendSections([ .tabOptions, .origins ])
|
||||||
snapshot.appendItems(Array(blockedScripts))
|
snapshot.appendItems([ Self.enableScriptsForTabItem ], toSection: .tabOptions)
|
||||||
|
snapshot.appendItems(originItems, toSection: .origins)
|
||||||
dataSource.apply(snapshot)
|
dataSource.apply(snapshot)
|
||||||
|
|
||||||
self.dataSource = dataSource
|
self.dataSource = dataSource
|
||||||
@@ -91,6 +189,7 @@ class ScriptPolicyViewController: UIViewController, UICollectionViewDelegate
|
|||||||
navigationItem.rightBarButtonItem = UIBarButtonItem(systemItem: .done, primaryAction: UIAction(handler: { action in
|
navigationItem.rightBarButtonItem = UIBarButtonItem(systemItem: .done, primaryAction: UIAction(handler: { action in
|
||||||
if self.didChangeScriptPolicy {
|
if self.didChangeScriptPolicy {
|
||||||
self.delegate?.didChangeScriptPolicy()
|
self.delegate?.didChangeScriptPolicy()
|
||||||
|
self.delegate?.setScriptsEnabledForTab(self.allowScriptsForTab)
|
||||||
}
|
}
|
||||||
|
|
||||||
self.dismiss(animated: true, completion: nil)
|
self.dismiss(animated: true, completion: nil)
|
||||||
|
|||||||
@@ -22,4 +22,11 @@ extension CGRect
|
|||||||
|
|
||||||
return rect
|
return rect
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public func centeredY(inRect: CGRect) -> CGRect {
|
||||||
|
var rect = self
|
||||||
|
rect.origin.y = CGRound((inRect.height - rect.height) / 2.0)
|
||||||
|
|
||||||
|
return rect
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,4 +14,11 @@ extension UIEdgeInsets
|
|||||||
return UIEdgeInsets(top: -top, left: -left, bottom: -bottom, right: -right)
|
return UIEdgeInsets(top: -top, left: -left, bottom: -bottom, right: -right)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func subtracting(_ other: UIEdgeInsets) -> UIEdgeInsets {
|
||||||
|
return UIEdgeInsets(top: self.top - other.top,
|
||||||
|
left: self.left - other.left,
|
||||||
|
bottom: self.bottom - other.bottom,
|
||||||
|
right: self.right - other.right)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,8 +19,8 @@ NS_ASSUME_NONNULL_BEGIN
|
|||||||
|
|
||||||
@class SBRProcessBundleBridge;
|
@class SBRProcessBundleBridge;
|
||||||
@protocol SBRProcessBundleBridgeDelegate <NSObject>
|
@protocol SBRProcessBundleBridgeDelegate <NSObject>
|
||||||
|
- (void)webProcess:(SBRProcessBundleBridge *)bridge didAllowScriptResourceFromOrigin:(NSString *)origin;
|
||||||
- (void)webProcess:(SBRProcessBundleBridge *)bridge didBlockScriptResourceFromOrigin:(NSString *)origin;
|
- (void)webProcess:(SBRProcessBundleBridge *)bridge didBlockScriptResourceFromOrigin:(NSString *)origin;
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
||||||
@interface SBRProcessBundleBridge : NSObject
|
@interface SBRProcessBundleBridge : NSObject
|
||||||
@@ -28,7 +28,8 @@ NS_ASSUME_NONNULL_BEGIN
|
|||||||
@property (nonatomic, readonly) WKWebView *webView;
|
@property (nonatomic, readonly) WKWebView *webView;
|
||||||
|
|
||||||
@property (nonatomic, weak) id<SBRProcessBundleBridgeDelegate> delegate;
|
@property (nonatomic, weak) id<SBRProcessBundleBridgeDelegate> delegate;
|
||||||
@property (nonatomic, weak) id<SBRResourceOriginPolicyDataSource> policyDataSource;
|
@property (nonatomic, strong) id<SBRResourceOriginPolicyDataSource> policyDataSource;
|
||||||
|
@property (nonatomic, assign) BOOL allowAllScripts; // default is NO
|
||||||
|
|
||||||
- (void)policyDataSourceDidChange;
|
- (void)policyDataSourceDidChange;
|
||||||
|
|
||||||
|
|||||||
@@ -37,6 +37,12 @@
|
|||||||
|
|
||||||
// Set up process pool
|
// Set up process pool
|
||||||
WKProcessPool *processPool = [[WKProcessPool alloc] _initWithConfiguration:poolConfiguration];
|
WKProcessPool *processPool = [[WKProcessPool alloc] _initWithConfiguration:poolConfiguration];
|
||||||
|
|
||||||
|
// Initialize allowed origins now
|
||||||
|
NSArray<NSString *> *allowedOrigins = [[_policyDataSource allowedOriginsForScriptResources] allObjects];
|
||||||
|
[processPool _setObject:allowedOrigins forBundleParameter:SBRGetAllowedOriginsKey()];
|
||||||
|
[processPool _setObject:@(_allowAllScripts) forBundleParameter:SBRGetAllScriptsAllowedKey()];
|
||||||
|
|
||||||
WKWebViewConfiguration *configuration = [[WKWebViewConfiguration alloc] init];
|
WKWebViewConfiguration *configuration = [[WKWebViewConfiguration alloc] init];
|
||||||
configuration.processPool = processPool;
|
configuration.processPool = processPool;
|
||||||
|
|
||||||
@@ -79,4 +85,9 @@
|
|||||||
[_webProcessProxy syncAllowedResourceOrigins:allowedOrigins];
|
[_webProcessProxy syncAllowedResourceOrigins:allowedOrigins];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
- (void)setAllowAllScripts:(BOOL)allowAllScripts
|
||||||
|
{
|
||||||
|
[_webProcessProxy setAllScriptsAllowed:allowAllScripts];
|
||||||
|
}
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|||||||
@@ -17,8 +17,10 @@
|
|||||||
#import <WebKit/WKWebProcessPlugInLoadDelegate.h>
|
#import <WebKit/WKWebProcessPlugInLoadDelegate.h>
|
||||||
|
|
||||||
@interface SBRProcessPlugin () <WKWebProcessPlugInLoadDelegate, SBRWebProcessProxy>
|
@interface SBRProcessPlugin () <WKWebProcessPlugInLoadDelegate, SBRWebProcessProxy>
|
||||||
|
|
||||||
@property (nonatomic, strong) id<SBRWebProcessDelegate> processDelegate;
|
@property (nonatomic, strong) id<SBRWebProcessDelegate> processDelegate;
|
||||||
@property (nonatomic, strong) NSMutableSet<NSString *> *allowedResourceOrigins;
|
@property (nonatomic, strong) NSMutableSet<NSString *> *allowedResourceOrigins;
|
||||||
|
@property (nonatomic, assign) BOOL allScriptsAllowed;
|
||||||
@end
|
@end
|
||||||
|
|
||||||
@implementation SBRProcessPlugin
|
@implementation SBRProcessPlugin
|
||||||
@@ -41,8 +43,20 @@
|
|||||||
_allowedResourceOrigins = [NSMutableSet setWithArray:allowedOrigins];
|
_allowedResourceOrigins = [NSMutableSet setWithArray:allowedOrigins];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
- (void)setAllScriptsAllowed:(BOOL)allScriptsAllowed
|
||||||
|
{
|
||||||
|
_allScriptsAllowed = allScriptsAllowed;
|
||||||
|
}
|
||||||
|
|
||||||
#pragma mark <WKWebProcessPlugIn>
|
#pragma mark <WKWebProcessPlugIn>
|
||||||
|
|
||||||
|
- (void)webProcessPlugIn:(WKWebProcessPlugInController *)plugInController initializeWithObject:(id)initializationObject
|
||||||
|
{
|
||||||
|
_allowedResourceOrigins = [[plugInController parameters] valueForKey:SBRGetAllowedOriginsKey()];
|
||||||
|
_allScriptsAllowed = [[[plugInController parameters] valueForKey:SBRGetAllScriptsAllowedKey()] boolValue];
|
||||||
|
NSLog(@"Got %lu allowed script origins at initialization", (unsigned long)_allowedResourceOrigins.count);
|
||||||
|
}
|
||||||
|
|
||||||
- (void)webProcessPlugIn:(WKWebProcessPlugInController *)plugInController didCreateBrowserContextController:(WKWebProcessPlugInBrowserContextController *)browserContextController
|
- (void)webProcessPlugIn:(WKWebProcessPlugInController *)plugInController didCreateBrowserContextController:(WKWebProcessPlugInBrowserContextController *)browserContextController
|
||||||
{
|
{
|
||||||
_WKRemoteObjectInterface *proxyInterface = [_WKRemoteObjectInterface remoteObjectInterfaceWithProtocol:@protocol(SBRWebProcessProxy)];
|
_WKRemoteObjectInterface *proxyInterface = [_WKRemoteObjectInterface remoteObjectInterfaceWithProtocol:@protocol(SBRWebProcessProxy)];
|
||||||
@@ -61,6 +75,9 @@
|
|||||||
- (NSURLRequest *)webProcessPlugInBrowserContextController:(WKWebProcessPlugInBrowserContextController *)controller frame:(WKWebProcessPlugInFrame *)frame willSendRequest:(NSURLRequest *)request redirectResponse:(NSURLResponse *)redirectResponse
|
- (NSURLRequest *)webProcessPlugInBrowserContextController:(WKWebProcessPlugInBrowserContextController *)controller frame:(WKWebProcessPlugInFrame *)frame willSendRequest:(NSURLRequest *)request redirectResponse:(NSURLResponse *)redirectResponse
|
||||||
{
|
{
|
||||||
NSLog(@"SBRProcessPlugin: Sending request: %@", request);
|
NSLog(@"SBRProcessPlugin: Sending request: %@", request);
|
||||||
|
if (_allowedResourceOrigins == nil) {
|
||||||
|
NSLog(@"Allowed resource origins should not be nil!!!!");
|
||||||
|
}
|
||||||
|
|
||||||
NSURL *requestURL = [request URL];
|
NSURL *requestURL = [request URL];
|
||||||
NSString *originString = [requestURL host];
|
NSString *originString = [requestURL host];
|
||||||
@@ -68,7 +85,7 @@
|
|||||||
if (requestExtension.length > 0 && [requestExtension isEqualToString:@"js"]) {
|
if (requestExtension.length > 0 && [requestExtension isEqualToString:@"js"]) {
|
||||||
[[self processDelegate] webProcessDidLoadScriptWithOrigin:originString];
|
[[self processDelegate] webProcessDidLoadScriptWithOrigin:originString];
|
||||||
|
|
||||||
if ([_allowedResourceOrigins containsObject:originString]) {
|
if ([self allScriptsAllowed] || [_allowedResourceOrigins containsObject:originString]) {
|
||||||
NSLog(@"SBRProcessPlugin: Allowing whitelisted requestURL: %@", requestURL);
|
NSLog(@"SBRProcessPlugin: Allowing whitelisted requestURL: %@", requestURL);
|
||||||
} else {
|
} else {
|
||||||
NSLog(@"SBRProcessPlugin: Blocking requestURL: %@", requestURL);
|
NSLog(@"SBRProcessPlugin: Blocking requestURL: %@", requestURL);
|
||||||
|
|||||||
@@ -7,9 +7,18 @@
|
|||||||
|
|
||||||
#import <Foundation/Foundation.h>
|
#import <Foundation/Foundation.h>
|
||||||
|
|
||||||
|
static inline NSString* SBRGetAllowedOriginsKey() {
|
||||||
|
return @"allowedOrigins";
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline NSString* SBRGetAllScriptsAllowedKey() {
|
||||||
|
return @"allScriptsAllowed";
|
||||||
|
}
|
||||||
|
|
||||||
@protocol SBRWebProcessProxy <NSObject>
|
@protocol SBRWebProcessProxy <NSObject>
|
||||||
|
|
||||||
- (void)hello;
|
- (void)hello;
|
||||||
- (void)syncAllowedResourceOrigins:(NSArray<NSString *> *)allowedOrigins;
|
- (void)syncAllowedResourceOrigins:(NSArray<NSString *> *)allowedOrigins;
|
||||||
|
- (void)setAllScriptsAllowed:(BOOL)allowed;
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|||||||
Reference in New Issue
Block a user