Feature: Adds open in background (Shift+Cmd)
This commit is contained in:
@@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|
||||||
|
if !loadInBackground {
|
||||||
self.tab = newTab
|
self.tab = newTab
|
||||||
|
|
||||||
if url == nil && traitCollection.userInterfaceIdiom == .mac {
|
if url == nil && traitCollection.userInterfaceIdiom == .mac {
|
||||||
self.toolbarController.urlBar.textField.becomeFirstResponder()
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user