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,148 @@
/* 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 Shared
import OnePasswordExtension
private let log = Logger.browserLogger
class ShareExtensionHelper: NSObject {
fileprivate weak var selectedTab: Tab?
fileprivate let selectedURL: URL
fileprivate var onePasswordExtensionItem: NSExtensionItem!
fileprivate let browserFillIdentifier = "org.appextension.fill-browser-action"
init(url: URL, tab: Tab?) {
self.selectedURL = tab?.canonicalURL?.displayURL ?? url
self.selectedTab = tab
}
func createActivityViewController(_ completionHandler: @escaping (_ completed: Bool, _ activityType: String?) -> Void) -> UIActivityViewController {
var activityItems = [AnyObject]()
let printInfo = UIPrintInfo(dictionary: nil)
let absoluteString = selectedTab?.url?.absoluteString ?? selectedURL.absoluteString
printInfo.jobName = absoluteString
printInfo.outputType = .general
activityItems.append(printInfo)
if let tab = selectedTab {
activityItems.append(TabPrintPageRenderer(tab: tab))
}
if let title = selectedTab?.title {
activityItems.append(TitleActivityItemProvider(title: title))
}
activityItems.append(self)
let activityViewController = UIActivityViewController(activityItems: activityItems, applicationActivities: nil)
// Hide 'Add to Reading List' which currently uses Safari.
// We would also hide View Later, if possible, but the exclusion list doesn't currently support
// third-party activity types (rdar://19430419).
activityViewController.excludedActivityTypes = [
UIActivityType.addToReadingList,
]
// This needs to be ready by the time the share menu has been displayed and
// activityViewController(activityViewController:, activityType:) is called,
// which is after the user taps the button. So a million cycles away.
findLoginExtensionItem()
activityViewController.completionWithItemsHandler = { activityType, completed, returnedItems, activityError in
if !completed {
completionHandler(completed, activityType.map { $0.rawValue })
return
}
// Bug 1392418 - When copying a url using the share extension there are 2 urls in the pasteboard.
// Make sure the pasteboard only has one url.
if let url = UIPasteboard.general.urls?.first {
UIPasteboard.general.urls = [url]
}
if self.isPasswordManagerActivityType(activityType.map { $0.rawValue }) {
if let logins = returnedItems {
self.fillPasswords(logins as [AnyObject])
}
}
completionHandler(completed, activityType.map { $0.rawValue })
}
return activityViewController
}
}
extension ShareExtensionHelper: UIActivityItemSource {
func activityViewControllerPlaceholderItem(_ activityViewController: UIActivityViewController) -> Any {
return selectedURL
}
// IMPORTANT: This method needs Swift compiler optimization DISABLED to prevent a nasty
// crash from happening in release builds. It seems as though the check for `nil` may
// get removed by the optimizer which leads to a crash when that happens.
@_semantics("optimize.sil.never") func activityViewController(_ activityViewController: UIActivityViewController, itemForActivityType activityType: UIActivityType) -> Any? {
// activityType actually is nil sometimes (in the simulator at least)
if activityType != nil && isPasswordManagerActivityType(activityType.rawValue) {
return onePasswordExtensionItem
} else {
// Return the URL for the selected tab. If we are in reader view then decode
// it so that we copy the original and not the internal localhost one.
return selectedURL.isReaderModeURL ? selectedURL.decodeReaderModeURL : selectedURL
}
}
func activityViewController(_ activityViewController: UIActivityViewController, dataTypeIdentifierForActivityType activityType: UIActivityType?) -> String {
if let type = activityType, isPasswordManagerActivityType(type.rawValue) {
return browserFillIdentifier
}
return activityType == nil ? browserFillIdentifier : kUTTypeURL as String
}
}
private extension ShareExtensionHelper {
func isPasswordManagerActivityType(_ activityType: String?) -> Bool {
// A 'password' substring covers the most cases, such as pwsafe and 1Password.
// com.agilebits.onepassword-ios.extension
// com.app77.ios.pwsafe2.find-login-action-password-actionExtension
// If your extension's bundle identifier does not contain "password", simply submit a pull request by adding your bundle identifier.
return (activityType?.range(of: "password") != nil)
|| (activityType == "com.lastpass.ilastpass.LastPassExt")
|| (activityType == "in.sinew.Walletx.WalletxExt")
|| (activityType == "com.8bit.bitwarden.find-login-action-extension")
}
func findLoginExtensionItem() {
guard let selectedWebView = selectedTab?.webView else {
return
}
// Add 1Password to share sheet
OnePasswordExtension.shared().createExtensionItem(forWebView: selectedWebView, completion: {(extensionItem, error) -> Void in
if extensionItem == nil {
log.error("Failed to create the password manager extension item: \(error.debugDescription).")
return
}
// Set the 1Password extension item property
self.onePasswordExtensionItem = extensionItem
})
}
func fillPasswords(_ returnedItems: [AnyObject]) {
guard let selectedWebView = selectedTab?.webView else {
return
}
OnePasswordExtension.shared().fillReturnedItems(returnedItems, intoWebView: selectedWebView, completion: { (success, returnedItemsError) -> Void in
if !success {
log.error("Failed to fill item into webview: \(returnedItemsError ??? "nil").")
}
})
}
}

View file

@ -0,0 +1,33 @@
/* 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
/// This Activity Item Provider subclass does two things that are non-standard behaviour:
///
/// * We return NSNull if the calling activity is not supposed to see the title. For example the Copy action, which should only paste the URL. We also include Message and Mail to have parity with what Safari exposes.
/// * We set the subject of the item to the title, this means it will correctly be used when sharing to for example Mail. Again parity with Safari.
///
/// Note that not all applications use the Subject. For example OmniFocus ignores it, so we need to do both.
class TitleActivityItemProvider: UIActivityItemProvider {
static let activityTypesToIgnore = [UIActivityType.copyToPasteboard, UIActivityType.message, UIActivityType.mail]
init(title: String) {
super.init(placeholderItem: title)
}
override var item: Any {
if let activityType = activityType {
if TitleActivityItemProvider.activityTypesToIgnore.contains(activityType) {
return NSNull()
}
}
return placeholderItem! as AnyObject
}
override func activityViewController(_ activityViewController: UIActivityViewController, subjectForActivityType activityType: UIActivityType?) -> String {
return placeholderItem as! String
}
}