Feature: Adds open in background (Shift+Cmd)

This commit is contained in:
James Magahern
2022-02-21 16:01:01 -08:00
parent 224a39b64f
commit f32c246e35
6 changed files with 86 additions and 32 deletions

View File

@@ -59,9 +59,11 @@ extension BrowserViewController: ShortcutResponder
guard let press = presses.first else { return } guard let press = presses.first else { return }
if let key = press.key { 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 self.commandKeyHeld = isDown
} else if key.keyCode == .keyboardLeftShift || key.keyCode == .keyboardRightShift {
self.shiftKeyHeld = isDown
} }
} }
} }

View File

@@ -78,8 +78,7 @@ extension BrowserViewController: WKNavigationDelegate, WKUIDelegate
decisionHandler(.cancel, preferences) decisionHandler(.cancel, preferences)
// Start navigation in a new tab // Start navigation in a new tab
let tab = tabController.createNewTab(url: navigationAction.request.url) createNewTab(withURL: navigationAction.request.url, loadInBackground: self.shiftKeyHeld)
self.tab = tab
// Reset this flag. // Reset this flag.
commandKeyHeld = false commandKeyHeld = false
@@ -140,16 +139,23 @@ extension BrowserViewController: WKNavigationDelegate, WKUIDelegate
discoverabilityTitle: nil, discoverabilityTitle: nil,
attributes: [], attributes: [],
state: .off) { [unowned self] _ in state: .off) { [unowned self] _ in
let newTab = tabController.createNewTab(url: elementInfo.linkURL) self.createNewTab(withURL: elementInfo.linkURL, loadInBackground: false)
self.tab = newTab
} }
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", return UIMenu(title: elementInfo.linkURL?.absoluteString ?? "Link",
image: nil, image: nil,
identifier: nil, identifier: nil,
options: .displayInline, options: .displayInline,
children: [ openInNewTab ] + menuElements) children: [ openInNewTab, openInBackground ] + menuElements)
} }
completionHandler(menuConfig) completionHandler(menuConfig)

View File

@@ -26,14 +26,13 @@ class BrowserViewController: UIViewController
override var canBecomeFirstResponder: Bool { true } override var canBecomeFirstResponder: Bool { true }
private var titleObservation: NSKeyValueObservation?
private var loadingObservation: NSKeyValueObservation? private var loadingObservation: NSKeyValueObservation?
private var backButtonObservation: NSKeyValueObservation? private var backButtonObservation: NSKeyValueObservation?
private var forwardButtonObservation: NSKeyValueObservation? private var forwardButtonObservation: NSKeyValueObservation?
private var hasSecureContentObservation: NSKeyValueObservation? private var hasSecureContentObservation: NSKeyValueObservation?
private var activeTabObservation: AnyCancellable? private var activeTabObservation: AnyCancellable?
private var faviconObservation: AnyCancellable?
internal var shiftKeyHeld: Bool = false
internal var commandKeyHeld: Bool = false internal var commandKeyHeld: Bool = false
internal var windowButtonHeld: Bool { internal var windowButtonHeld: Bool {
get { toolbarController.newTabButton.isTracking } get { toolbarController.newTabButton.isTracking }
@@ -65,6 +64,8 @@ class BrowserViewController: UIViewController
self.tabBarViewController = TabBarViewController(tabController: tabController) self.tabBarViewController = TabBarViewController(tabController: tabController)
super.init(nibName: nil, bundle: nil) super.init(nibName: nil, bundle: nil)
self.tabController.controllerDelegate = self
addChild(toolbarController) addChild(toolbarController)
addChild(findOnPageController) addChild(findOnPageController)
addChild(tabBarViewController) addChild(tabBarViewController)
@@ -168,11 +169,11 @@ class BrowserViewController: UIViewController
alert.dismiss(animated: true, completion: nil) 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) alert.dismiss(animated: true, completion: nil)
// Clears out the error state // Clears out the error state
toolbarController.urlBar.loadProgress = .complete self.toolbarController.urlBar.loadProgress = .complete
})) }))
self.present(alert, animated: true, completion: nil) self.present(alert, animated: true, completion: nil)
@@ -247,13 +248,13 @@ class BrowserViewController: UIViewController
DispatchQueue.main.async { DispatchQueue.main.async {
documentControls.dismiss(animated: true, completion: nil) documentControls.dismiss(animated: true, completion: nil)
let readableViewController = ReaderViewController(readableHTMLString: string, baseURL: tab.bridge.webView.url) let readableViewController = ReaderViewController(readableHTMLString: string, baseURL: self.tab.bridge.webView.url)
readableViewController.title = tab.bridge.webView.title readableViewController.title = self.tab.bridge.webView.title
readableViewController.darkModeEnabled = tab.bridge.darkModeEnabled readableViewController.darkModeEnabled = self.tab.bridge.darkModeEnabled
readableViewController.delegate = self readableViewController.delegate = self
let navigationController = UINavigationController(rootViewController: readableViewController) let navigationController = UINavigationController(rootViewController: readableViewController)
present(navigationController, animated: true, completion: nil) self.present(navigationController, animated: true, completion: nil)
} }
} }
}, for: .touchUpInside) }, for: .touchUpInside)
@@ -310,12 +311,16 @@ class BrowserViewController: UIViewController
} }
// Show tab bar view? // Show tab bar view?
browserView.tabBarViewVisible = tabController.tabs.count > 1 self.updateTabBarVisibility()
}) })
self.view = browserView self.view = browserView
} }
internal func updateTabBarVisibility() {
browserView.tabBarViewVisible = tabController.tabs.count > 1
}
internal func showShareSheetForCurrentURL(fromViewController: UIViewController?) { internal func showShareSheetForCurrentURL(fromViewController: UIViewController?) {
guard let url = self.webView.url else { return } guard let url = self.webView.url else { return }
@@ -455,9 +460,6 @@ class BrowserViewController: UIViewController
// Title observer // Title observer
updateTitleAndURL(forWebView: webView) updateTitleAndURL(forWebView: webView)
titleObservation = webView.observe(\.title, changeHandler: { [unowned self] (webView, observedChange) in
self.updateTitleAndURL(forWebView: webView)
})
// Back/forward observer // Back/forward observer
toolbarController.backButton.isEnabled = webView.canGoBack toolbarController.backButton.isEnabled = webView.canGoBack
@@ -476,12 +478,6 @@ class BrowserViewController: UIViewController
browserView.titlebarView.showsSecurityIndicator = webView.hasOnlySecureContent browserView.titlebarView.showsSecurityIndicator = webView.hasOnlySecureContent
}) })
// Favicon observation
faviconObservation = tab.$favicon.receive(on: DispatchQueue.main)
.sink { [unowned self] _ in
updateTitleAndURL(forWebView: webView)
}
// Script blocker button // Script blocker button
updateScriptBlockerButton() updateScriptBlockerButton()
@@ -527,13 +523,26 @@ class BrowserViewController: UIViewController
iconView.isEnabled = (webView.url != nil) 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) let newTab = tabController.createNewTab(url: url)
self.tab = newTab
if url == nil && traitCollection.userInterfaceIdiom == .mac { if !loadInBackground {
self.toolbarController.urlBar.textField.becomeFirstResponder() 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) 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)
}
}

View File

@@ -70,8 +70,9 @@ class Tab: NSObject, SBRProcessBundleBridgeDelegate
public var allowedScriptOrigins = Set<String>() public var allowedScriptOrigins = Set<String>()
public var blockedScriptOrigins = Set<String>() public var blockedScriptOrigins = Set<String>()
private var titleObservation: NSKeyValueObservation? public var titleObservation: NSKeyValueObservation?
private var urlObservation: NSKeyValueObservation? public var urlObservation: NSKeyValueObservation?
public var faviconObservation: AnyCancellable?
convenience init(policyManager: ResourcePolicyManager) { convenience init(policyManager: ResourcePolicyManager) {
self.init(url: nil, policyManager: policyManager, webViewConfiguration: nil) self.init(url: nil, policyManager: policyManager, webViewConfiguration: nil)

View File

@@ -30,7 +30,7 @@ class TabBarViewController: UIViewController, TabBarViewDataSource, TabBarViewDe
tabBarView.reloadTabs() tabBarView.reloadTabs()
tabObserver = tabController.$tabs.sink(receiveValue: { [unowned self] (newTabs: [Tab]) in 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 activeTabIndexObserver = tabController.$activeTabIndex.sink(receiveValue: { [unowned self] (activeIndex: Int) in

View File

@@ -7,12 +7,18 @@
import Foundation import Foundation
protocol TabControllerDelegate: AnyObject {
func tabController(_ controller: TabController, didUpdateTitle: String, forTab: Tab)
func tabController(_ controller: TabController, didUpdateFavicon: UIImage?, forTab: Tab)
}
class TabController class TabController
{ {
@Published var tabs: [Tab] = [] @Published var tabs: [Tab] = []
@Published var activeTabIndex: Int = 0 @Published var activeTabIndex: Int = 0
var policyManager = ResourcePolicyManager() var policyManager = ResourcePolicyManager()
weak var controllerDelegate: TabControllerDelegate?
init() { init() {
// TODO: load tabs from disk. // TODO: load tabs from disk.
@@ -39,6 +45,20 @@ class TabController
tabs.append(tab) 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 return tab
} }