// // Tab.swift // SBrowser // // Created by James Magahern on 7/29/20. // import UIKit import Combine protocol TabDelegate: class { func didBlockScriptOrigin(_ origin: String, forTab: Tab) } class Tab: NSObject, SBRProcessBundleBridgeDelegate { public weak var delegate: TabDelegate? public let homeURL: URL? public let bridge: ProcessBundleBridge public var webView: WKWebView { if self.loadedWebView == nil { self.loadedWebView = bridge.webView if let homeURL = homeURL { beginLoadingURL(homeURL) } } return bridge.webView } public var policyManager: ResourcePolicyManager private var loadedWebView: WKWebView? = nil public var title: String? { loadedWebView?.title } public var url: URL? { loadedWebView?.url ?? self.homeURL } public var javaScriptEnabled: Bool = false { didSet { bridge.allowAllScripts = javaScriptEnabled } } public lazy var colorTheme: [UIColor] = { // Looks really cool as-is, but maybe compute a color based on the // title or URL to make it show up the same for each website? return [ UIColor( hue: CGFloat.random(in: 0...1.0), saturation: 0.89, brightness: 0.3, alpha: 1.0 ), UIColor( hue: CGFloat.random(in: 0...1.0), saturation: 1.0, brightness: 0.7, alpha: 1.0 ), ] }() public var identifier = UUID() @Published public var favicon: UIImage? private var faviconHost: String? private var faviconRequest: AnyCancellable? public var allowedScriptOrigins = Set() public var blockedScriptOrigins = Set() private var titleObservation: NSKeyValueObservation? private var urlObservation: NSKeyValueObservation? convenience init(policyManager: ResourcePolicyManager) { self.init(url: nil, policyManager: policyManager, webViewConfiguration: nil) } convenience init(urlString: String, policyManager: ResourcePolicyManager) { self.init(url: URL(string: urlString), policyManager: policyManager, webViewConfiguration: nil) } init(url: URL?, policyManager: ResourcePolicyManager, webViewConfiguration: WKWebViewConfiguration?) { self.homeURL = url self.policyManager = policyManager self.bridge = ProcessBundleBridge(webViewConfiguration: webViewConfiguration) self.bridge.policyDataSource = policyManager super.init() bridge.delegate = self } deinit { bridge.tearDown() } func beginLoadingURL(_ url: URL) { let request = URLRequest(url: url) webView.load(request) } // MARK: SBRProcessBundleBridgeDelegate func webProcess(_ bridge: ProcessBundleBridge, didAllowScriptResourceFromOrigin origin: String) { print("Allowed script resource from origin: \(origin)") allowedScriptOrigins.formUnion([ origin ]) } func webProcess(_ bridge: ProcessBundleBridge, didBlockScriptResourceFromOrigin origin: String) { print("Blocked script resource from origin: \(origin)") blockedScriptOrigins.formUnion([ origin ]) delegate?.didBlockScriptOrigin(origin, forTab: self) } func updateFaviconForURL(_ url: URL) { if let faviconHost = faviconHost, url.host == faviconHost {} else { guard var faviconURLComponents = URLComponents(url: url, resolvingAgainstBaseURL: false) else { return } faviconURLComponents.path = "/favicon.ico" let defaultImage = UIImage(systemName: "globe") guard let faviconURL = faviconURLComponents.url else { return } faviconRequest = URLSession.shared.dataTaskPublisher(for: faviconURL) .map { (data: Data, response: URLResponse) -> UIImage? in UIImage(data: data) } .replaceError(with: defaultImage) .replaceNil(with: defaultImage) .assign(to: \.favicon, on: self) } } }