From f32c246e3551f2859dcb70f009a6c33f7f16d1d8 Mon Sep 17 00:00:00 2001 From: James Magahern Date: Mon, 21 Feb 2022 16:01:01 -0800 Subject: [PATCH] Feature: Adds open in background (Shift+Cmd) --- .../BrowserViewController+Keyboard.swift | 6 +- ...BrowserViewController+WebKitDelegate.swift | 16 +++-- App/Browser View/BrowserViewController.swift | 69 +++++++++++++------ App/Tabs/Tab.swift | 5 +- App/Tabs/TabBarViewController.swift | 2 +- App/Tabs/TabController.swift | 20 ++++++ 6 files changed, 86 insertions(+), 32 deletions(-) diff --git a/App/Browser View/BrowserViewController+Keyboard.swift b/App/Browser View/BrowserViewController+Keyboard.swift index 9ce378b..982b166 100644 --- a/App/Browser View/BrowserViewController+Keyboard.swift +++ b/App/Browser View/BrowserViewController+Keyboard.swift @@ -59,9 +59,11 @@ extension BrowserViewController: ShortcutResponder guard let press = presses.first else { return } if let key = press.key { - if key.modifierFlags == [.command] { - let isDown = press.phase == .began || press.phase == .changed || press.phase == .stationary + let isDown = press.phase == .began || press.phase == .changed || press.phase == .stationary + if key.keyCode == .keyboardLeftGUI || key.keyCode == .keyboardRightGUI { self.commandKeyHeld = isDown + } else if key.keyCode == .keyboardLeftShift || key.keyCode == .keyboardRightShift { + self.shiftKeyHeld = isDown } } } diff --git a/App/Browser View/BrowserViewController+WebKitDelegate.swift b/App/Browser View/BrowserViewController+WebKitDelegate.swift index 8bafa63..472fc62 100644 --- a/App/Browser View/BrowserViewController+WebKitDelegate.swift +++ b/App/Browser View/BrowserViewController+WebKitDelegate.swift @@ -78,8 +78,7 @@ extension BrowserViewController: WKNavigationDelegate, WKUIDelegate decisionHandler(.cancel, preferences) // Start navigation in a new tab - let tab = tabController.createNewTab(url: navigationAction.request.url) - self.tab = tab + createNewTab(withURL: navigationAction.request.url, loadInBackground: self.shiftKeyHeld) // Reset this flag. commandKeyHeld = false @@ -140,16 +139,23 @@ extension BrowserViewController: WKNavigationDelegate, WKUIDelegate discoverabilityTitle: nil, attributes: [], state: .off) { [unowned self] _ in - let newTab = tabController.createNewTab(url: elementInfo.linkURL) - self.tab = newTab + self.createNewTab(withURL: elementInfo.linkURL, loadInBackground: false) } + let openInBackground = UIAction(title: "Open in Background", + image: UIImage(systemName: "plus.rectangle.on.rectangle"), + identifier: nil, + discoverabilityTitle: nil, + attributes: [], + state: .off) { [unowned self] _ in + self.createNewTab(withURL: elementInfo.linkURL, loadInBackground: true) + } return UIMenu(title: elementInfo.linkURL?.absoluteString ?? "Link", image: nil, identifier: nil, options: .displayInline, - children: [ openInNewTab ] + menuElements) + children: [ openInNewTab, openInBackground ] + menuElements) } completionHandler(menuConfig) diff --git a/App/Browser View/BrowserViewController.swift b/App/Browser View/BrowserViewController.swift index 24f8d32..eb149d1 100644 --- a/App/Browser View/BrowserViewController.swift +++ b/App/Browser View/BrowserViewController.swift @@ -26,14 +26,13 @@ class BrowserViewController: UIViewController override var canBecomeFirstResponder: Bool { true } - private var titleObservation: NSKeyValueObservation? private var loadingObservation: NSKeyValueObservation? private var backButtonObservation: NSKeyValueObservation? private var forwardButtonObservation: NSKeyValueObservation? private var hasSecureContentObservation: NSKeyValueObservation? private var activeTabObservation: AnyCancellable? - private var faviconObservation: AnyCancellable? + internal var shiftKeyHeld: Bool = false internal var commandKeyHeld: Bool = false internal var windowButtonHeld: Bool { get { toolbarController.newTabButton.isTracking } @@ -65,6 +64,8 @@ class BrowserViewController: UIViewController self.tabBarViewController = TabBarViewController(tabController: tabController) super.init(nibName: nil, bundle: nil) + self.tabController.controllerDelegate = self + addChild(toolbarController) addChild(findOnPageController) addChild(tabBarViewController) @@ -168,11 +169,11 @@ class BrowserViewController: UIViewController alert.dismiss(animated: true, completion: nil) })) - alert.addAction(UIAlertAction(title: "OK", style: .default, handler: { _ in + alert.addAction(UIAlertAction(title: "OK", style: .default, handler: { [unowned self] _ in alert.dismiss(animated: true, completion: nil) // Clears out the error state - toolbarController.urlBar.loadProgress = .complete + self.toolbarController.urlBar.loadProgress = .complete })) self.present(alert, animated: true, completion: nil) @@ -247,13 +248,13 @@ class BrowserViewController: UIViewController DispatchQueue.main.async { documentControls.dismiss(animated: true, completion: nil) - let readableViewController = ReaderViewController(readableHTMLString: string, baseURL: tab.bridge.webView.url) - readableViewController.title = tab.bridge.webView.title - readableViewController.darkModeEnabled = tab.bridge.darkModeEnabled + let readableViewController = ReaderViewController(readableHTMLString: string, baseURL: self.tab.bridge.webView.url) + readableViewController.title = self.tab.bridge.webView.title + readableViewController.darkModeEnabled = self.tab.bridge.darkModeEnabled readableViewController.delegate = self let navigationController = UINavigationController(rootViewController: readableViewController) - present(navigationController, animated: true, completion: nil) + self.present(navigationController, animated: true, completion: nil) } } }, for: .touchUpInside) @@ -310,12 +311,16 @@ class BrowserViewController: UIViewController } // Show tab bar view? - browserView.tabBarViewVisible = tabController.tabs.count > 1 + self.updateTabBarVisibility() }) self.view = browserView } + internal func updateTabBarVisibility() { + browserView.tabBarViewVisible = tabController.tabs.count > 1 + } + internal func showShareSheetForCurrentURL(fromViewController: UIViewController?) { guard let url = self.webView.url else { return } @@ -455,9 +460,6 @@ class BrowserViewController: UIViewController // Title observer updateTitleAndURL(forWebView: webView) - titleObservation = webView.observe(\.title, changeHandler: { [unowned self] (webView, observedChange) in - self.updateTitleAndURL(forWebView: webView) - }) // Back/forward observer toolbarController.backButton.isEnabled = webView.canGoBack @@ -476,12 +478,6 @@ class BrowserViewController: UIViewController browserView.titlebarView.showsSecurityIndicator = webView.hasOnlySecureContent }) - // Favicon observation - faviconObservation = tab.$favicon.receive(on: DispatchQueue.main) - .sink { [unowned self] _ in - updateTitleAndURL(forWebView: webView) - } - // Script blocker button updateScriptBlockerButton() @@ -527,13 +523,26 @@ class BrowserViewController: UIViewController iconView.isEnabled = (webView.url != nil) } - public func createNewTab(withURL url: URL?) { + @discardableResult + public func createNewTab(withURL url: URL?, loadInBackground: Bool = false) -> Tab { let newTab = tabController.createNewTab(url: url) - self.tab = newTab - if url == nil && traitCollection.userInterfaceIdiom == .mac { - self.toolbarController.urlBar.textField.becomeFirstResponder() + if !loadInBackground { + self.tab = newTab + + if url == nil && traitCollection.userInterfaceIdiom == .mac { + self.toolbarController.urlBar.textField.becomeFirstResponder() + } + } else { + // Send this message to get it to load NOW, instead of waiting for it to show up + // in the view hierarchy. + tab.webView.didMoveToWindow() + + // Update tab bar now + updateTabBarVisibility() } + + return newTab } } @@ -661,3 +670,19 @@ extension BrowserViewController: MFMailComposeViewControllerDelegate controller.dismiss(animated: true, completion: nil) } } + +extension BrowserViewController: TabControllerDelegate +{ + func tabController(_ controller: TabController, didUpdateTitle: String, forTab tab: Tab) { + updateTitleAndURL(forWebView: tab.webView) + + // Fetch favicon in background, if applicable + if tab.favicon == nil, let url = tab.webView.url { + tab.updateFaviconForURL(url) + } + } + + func tabController(_ controller: TabController, didUpdateFavicon: UIImage?, forTab tab: Tab) { + updateTitleAndURL(forWebView: tab.webView) + } +} diff --git a/App/Tabs/Tab.swift b/App/Tabs/Tab.swift index 52b94df..d5b8c99 100644 --- a/App/Tabs/Tab.swift +++ b/App/Tabs/Tab.swift @@ -70,8 +70,9 @@ class Tab: NSObject, SBRProcessBundleBridgeDelegate public var allowedScriptOrigins = Set() public var blockedScriptOrigins = Set() - private var titleObservation: NSKeyValueObservation? - private var urlObservation: NSKeyValueObservation? + public var titleObservation: NSKeyValueObservation? + public var urlObservation: NSKeyValueObservation? + public var faviconObservation: AnyCancellable? convenience init(policyManager: ResourcePolicyManager) { self.init(url: nil, policyManager: policyManager, webViewConfiguration: nil) diff --git a/App/Tabs/TabBarViewController.swift b/App/Tabs/TabBarViewController.swift index b120256..0c348f5 100644 --- a/App/Tabs/TabBarViewController.swift +++ b/App/Tabs/TabBarViewController.swift @@ -30,7 +30,7 @@ class TabBarViewController: UIViewController, TabBarViewDataSource, TabBarViewDe tabBarView.reloadTabs() tabObserver = tabController.$tabs.sink(receiveValue: { [unowned self] (newTabs: [Tab]) in - DispatchQueue.main.async { tabBarView.reloadTabs() } + DispatchQueue.main.async { self.tabBarView.reloadTabs() } }) activeTabIndexObserver = tabController.$activeTabIndex.sink(receiveValue: { [unowned self] (activeIndex: Int) in diff --git a/App/Tabs/TabController.swift b/App/Tabs/TabController.swift index f3a5c3d..3d96b79 100644 --- a/App/Tabs/TabController.swift +++ b/App/Tabs/TabController.swift @@ -7,12 +7,18 @@ import Foundation +protocol TabControllerDelegate: AnyObject { + func tabController(_ controller: TabController, didUpdateTitle: String, forTab: Tab) + func tabController(_ controller: TabController, didUpdateFavicon: UIImage?, forTab: Tab) +} + class TabController { @Published var tabs: [Tab] = [] @Published var activeTabIndex: Int = 0 var policyManager = ResourcePolicyManager() + weak var controllerDelegate: TabControllerDelegate? init() { // TODO: load tabs from disk. @@ -39,6 +45,20 @@ class TabController tabs.append(tab) } + // Title observation + tab.titleObservation = tab.webView.observe(\.title, changeHandler: { [weak tab, weak self] (webView, change) in + if let tab = tab, let self = self, let delegate = self.controllerDelegate { + delegate.tabController(self, didUpdateTitle: webView.title ?? "", forTab: tab) + } + }) + + // Favicon Observation + tab.faviconObservation = tab.$favicon.receive(on: RunLoop.main).sink { [weak tab, weak self] val in + if let tab = tab, let self = self, let delegate = self.controllerDelegate { + delegate.tabController(self, didUpdateFavicon: val, forTab: tab) + } + } + return tab }