Dactyloidae iOS initial commit

This commit is contained in:
wuggy 2026-06-26 21:04:09 -07:00
commit 7154a0497e
2123 changed files with 197052 additions and 0 deletions

View file

@ -0,0 +1,100 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
import Shared
// Naming functions: use the suffix 'KeyCommand' for an additional level of namespacing (bug 1415830)
extension BrowserViewController {
@objc private func reloadTabKeyCommand() {
if homePanelController == nil {
tabManager.selectedTab?.reload()
}
}
@objc private func goBackKeyCommand() {
if tabManager.selectedTab?.canGoBack == true && homePanelController == nil {
tabManager.selectedTab?.goBack()
}
}
@objc private func goForwardKeyCommand() {
if tabManager.selectedTab?.canGoForward == true && homePanelController == nil {
tabManager.selectedTab?.goForward()
}
}
@objc private func findOnPageKeyCommand() {
if homePanelController == nil {
tab( (tabManager.selectedTab)!, didSelectFindInPageForSelection: "")
}
}
@objc private func selectLocationBarKeyCommand() {
scrollController.showToolbars(animated: true)
urlBar.tabLocationViewDidTapLocation(urlBar.locationView)
}
@objc private func newTabKeyCommand() {
openBlankNewTab(focusLocationField: false, isPrivate: false)
}
@objc private func newPrivateTabKeyCommand() {
openBlankNewTab(focusLocationField: false, isPrivate: true)
}
@objc private func closeTabKeyCommand() {
guard let currentTab = tabManager.selectedTab else {
return
}
tabManager.removeTab(currentTab)
}
@objc private func nextTabKeyCommand() {
guard let currentTab = tabManager.selectedTab else {
return
}
let tabs = currentTab.isPrivate ? tabManager.privateTabs : tabManager.normalTabs
if let index = tabs.index(of: currentTab), index + 1 < tabs.count {
tabManager.selectTab(tabs[index + 1])
} else if let firstTab = tabs.first {
tabManager.selectTab(firstTab)
}
}
@objc private func previousTabKeyCommand() {
guard let currentTab = tabManager.selectedTab else {
return
}
let tabs = currentTab.isPrivate ? tabManager.privateTabs : tabManager.normalTabs
if let index = tabs.index(of: currentTab), index - 1 < tabs.count && index != 0 {
tabManager.selectTab(tabs[index - 1])
} else if let lastTab = tabs.last {
tabManager.selectTab(lastTab)
}
}
override var keyCommands: [UIKeyCommand]? {
return [
UIKeyCommand(input: "r", modifierFlags: .command, action: #selector(BrowserViewController.reloadTabKeyCommand), discoverabilityTitle: Strings.ReloadPageTitle),
UIKeyCommand(input: "[", modifierFlags: .command, action: #selector(BrowserViewController.goBackKeyCommand), discoverabilityTitle: Strings.BackTitle),
UIKeyCommand(input: UIKeyInputLeftArrow, modifierFlags: .command, action: #selector(BrowserViewController.goBackKeyCommand), discoverabilityTitle: Strings.BackTitle),
UIKeyCommand(input: "]", modifierFlags: .command, action: #selector(BrowserViewController.goForwardKeyCommand), discoverabilityTitle: Strings.ForwardTitle),
UIKeyCommand(input: UIKeyInputRightArrow, modifierFlags: .command, action: #selector(BrowserViewController.goForwardKeyCommand), discoverabilityTitle: Strings.ForwardTitle),
UIKeyCommand(input: "f", modifierFlags: .command, action: #selector(BrowserViewController.findOnPageKeyCommand), discoverabilityTitle: Strings.FindTitle),
UIKeyCommand(input: "l", modifierFlags: .command, action: #selector(BrowserViewController.selectLocationBarKeyCommand), discoverabilityTitle: Strings.SelectLocationBarTitle),
UIKeyCommand(input: "t", modifierFlags: .command, action: #selector(BrowserViewController.newTabKeyCommand), discoverabilityTitle: Strings.NewTabTitle),
UIKeyCommand(input: "p", modifierFlags: [.command, .shift], action: #selector(BrowserViewController.newPrivateTabKeyCommand), discoverabilityTitle: Strings.NewPrivateTabTitle),
UIKeyCommand(input: "w", modifierFlags: .command, action: #selector(BrowserViewController.closeTabKeyCommand), discoverabilityTitle: Strings.CloseTabTitle),
UIKeyCommand(input: "\t", modifierFlags: .control, action: #selector(BrowserViewController.nextTabKeyCommand), discoverabilityTitle: Strings.ShowNextTabTitle),
UIKeyCommand(input: UIKeyInputRightArrow, modifierFlags: [.command, .shift], action: #selector(BrowserViewController.nextTabKeyCommand), discoverabilityTitle: Strings.ShowNextTabTitle),
UIKeyCommand(input: "\t", modifierFlags: [.control, .shift], action: #selector(BrowserViewController.previousTabKeyCommand), discoverabilityTitle: Strings.ShowPreviousTabTitle),
UIKeyCommand(input: UIKeyInputLeftArrow, modifierFlags: [.command, .shift], action: #selector(BrowserViewController.previousTabKeyCommand), discoverabilityTitle: Strings.ShowPreviousTabTitle),
]
}
}

View file

@ -0,0 +1,212 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
import Foundation
import WebKit
import Shared
private let log = Logger.browserLogger
extension WKNavigationAction {
/// Allow local requests only if the request is privileged.
var isAllowed: Bool {
guard let url = request.url else {
return true
}
return !url.isWebPage(includeDataURIs: false) || !url.isLocal || request.isPrivileged
}
}
extension BrowserViewController: WKNavigationDelegate {
func webView(_ webView: WKWebView, didStartProvisionalNavigation navigation: WKNavigation!) {
if tabManager.selectedTab?.webView !== webView {
return
}
updateFindInPageVisibility(visible: false)
// If we are going to navigate to a new page, hide the reader mode button. Unless we
// are going to a about:reader page. Then we keep it on screen: it will change status
// (orange color) as soon as the page has loaded.
if let url = webView.url {
if !url.isReaderModeURL {
urlBar.updateReaderModeState(ReaderModeState.unavailable)
hideReaderModeBar(animated: false)
}
// remove the open in overlay view if it is present
removeOpenInView()
}
}
// Recognize an Apple Maps URL. This will trigger the native app. But only if a search query is present. Otherwise
// it could just be a visit to a regular page on maps.apple.com.
fileprivate func isAppleMapsURL(_ url: URL) -> Bool {
if url.scheme == "http" || url.scheme == "https" {
if url.host == "maps.apple.com" && url.query != nil {
return true
}
}
return false
}
// Recognize a iTunes Store URL. These all trigger the native apps. Note that appstore.com and phobos.apple.com
// used to be in this list. I have removed them because they now redirect to itunes.apple.com. If we special case
// them then iOS will actually first open Safari, which then redirects to the app store. This works but it will
// leave a 'Back to Safari' button in the status bar, which we do not want.
fileprivate func isStoreURL(_ url: URL) -> Bool {
if url.scheme == "http" || url.scheme == "https" {
if url.host == "itunes.apple.com" {
return true
}
}
return false
}
// This is the place where we decide what to do with a new navigation action. There are a number of special schemes
// and http(s) urls that need to be handled in a different way. All the logic for that is inside this delegate
// method.
func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {
guard let url = navigationAction.request.url else {
decisionHandler(WKNavigationActionPolicy.cancel)
return
}
if url.scheme == "about" {
decisionHandler(WKNavigationActionPolicy.allow)
return
}
if !navigationAction.isAllowed && navigationAction.navigationType != .backForward {
log.warning("Denying unprivileged request: \(navigationAction.request)")
decisionHandler(WKNavigationActionPolicy.cancel)
return
}
// First special case are some schemes that are about Calling. We prompt the user to confirm this action. This
// gives us the exact same behaviour as Safari.
if url.scheme == "tel" || url.scheme == "facetime" || url.scheme == "facetime-audio" {
UIApplication.shared.openURL(url)
decisionHandler(WKNavigationActionPolicy.cancel)
return
}
// Second special case are a set of URLs that look like regular http links, but should be handed over to iOS
// instead of being loaded in the webview. Note that there is no point in calling canOpenURL() here, because
// iOS will always say yes. TODO Is this the same as isWhitelisted?
if isAppleMapsURL(url) {
UIApplication.shared.openURL(url)
decisionHandler(WKNavigationActionPolicy.cancel)
return
}
if let tab = tabManager.selectedTab, isStoreURL(url) {
decisionHandler(WKNavigationActionPolicy.cancel)
let alreadyShowingSnackbarOnThisTab = tab.bars.count > 0
if !alreadyShowingSnackbarOnThisTab {
TimerSnackBar.showAppStoreConfirmationBar(forTab: tab, appStoreURL: url)
}
return
}
// Handles custom mailto URL schemes.
if url.scheme == "mailto" {
if let mailToMetadata = url.mailToMetadata(), let mailScheme = self.profile.prefs.stringForKey(PrefsKeys.KeyMailToOption), mailScheme != "mailto" {
self.mailtoLinkHandler.launchMailClientForScheme(mailScheme, metadata: mailToMetadata, defaultMailtoURL: url)
} else {
UIApplication.shared.openURL(url)
}
LeanPlumClient.shared.track(event: .openedMailtoLink)
decisionHandler(WKNavigationActionPolicy.cancel)
return
}
// This is the normal case, opening a http or https url, which we handle by loading them in this WKWebView. We
// always allow this. Additionally, data URIs are also handled just like normal web pages.
if url.scheme == "http" || url.scheme == "https" || url.scheme == "data" || url.scheme == "blob" {
if navigationAction.navigationType == .linkActivated {
resetSpoofedUserAgentIfRequired(webView, newURL: url)
} else if navigationAction.navigationType == .backForward {
restoreSpoofedUserAgentIfRequired(webView, newRequest: navigationAction.request)
}
decisionHandler(WKNavigationActionPolicy.allow)
return
}
// Ignore JS navigated links, the intention is to match Safari and native WKWebView behaviour.
if navigationAction.navigationType == .linkActivated {
UIApplication.shared.open(url, options: [:]) { openedURL in
if !openedURL {
let alert = UIAlertController(title: Strings.UnableToOpenURLErrorTitle, message: Strings.UnableToOpenURLError, preferredStyle: .alert)
alert.addAction(UIAlertAction(title: UIConstants.OKString, style: UIAlertActionStyle.default, handler: nil))
self.present(alert, animated: true, completion: nil)
}
}
}
decisionHandler(WKNavigationActionPolicy.cancel)
}
func webView(_ webView: WKWebView, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
// If this is a certificate challenge, see if the certificate has previously been
// accepted by the user.
let origin = "\(challenge.protectionSpace.host):\(challenge.protectionSpace.port)"
if challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust,
let trust = challenge.protectionSpace.serverTrust,
let cert = SecTrustGetCertificateAtIndex(trust, 0), profile.certStore.containsCertificate(cert, forOrigin: origin) {
completionHandler(URLSession.AuthChallengeDisposition.useCredential, URLCredential(trust: trust))
return
}
guard challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodHTTPBasic ||
challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodHTTPDigest ||
challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodNTLM,
let tab = tabManager[webView] else {
completionHandler(URLSession.AuthChallengeDisposition.performDefaultHandling, nil)
return
}
// If this is a request to our local web server, use our private credentials.
if challenge.protectionSpace.host == "localhost" && challenge.protectionSpace.port == Int(WebServer.sharedInstance.server.port) {
completionHandler(.useCredential, WebServer.sharedInstance.credentials)
return
}
// The challenge may come from a background tab, so ensure it's the one visible.
tabManager.selectTab(tab)
let loginsHelper = tab.getContentScript(name: LoginsHelper.name()) as? LoginsHelper
Authenticator.handleAuthRequest(self, challenge: challenge, loginsHelper: loginsHelper).uponQueue(DispatchQueue.main) { res in
if let credentials = res.successValue {
completionHandler(.useCredential, credentials.credentials)
} else {
completionHandler(URLSession.AuthChallengeDisposition.rejectProtectionSpace, nil)
}
}
}
func webView(_ webView: WKWebView, didCommit navigation: WKNavigation!) {
guard let tab = tabManager[webView] else { return }
tab.url = webView.url
self.scrollController.resetZoomState()
if tabManager.selectedTab === tab {
updateUIForReaderHomeStateForTab(tab)
}
}
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
if let tab = tabManager[webView] {
navigateInTab(tab: tab, to: navigation)
}
}
}