-
Notifications
You must be signed in to change notification settings - Fork 3k
/
Copy pathShareExtensionHelper.swift
166 lines (138 loc) · 7.07 KB
/
ShareExtensionHelper.swift
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
/* 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 activities: [UIActivity]
// Wechat share extension doesn't like our default data ID which is a modified to support password managers.
fileprivate let customDataTypeIdentifers = ["com.tencent.xin.sharetimeline"]
init(url: URL, tab: Tab?, activities: [UIActivity]) {
self.selectedURL = url
self.selectedTab = tab
self.activities = activities
}
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: activities)
// 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.
if ShareExtensionHelper.isPasswordManagerExtensionAvailable() {
findLoginExtensionItem()
}
activityViewController.completionWithItemsHandler = { activityType, completed, returnedItems, activityError in
if !completed {
completionHandler(completed, activityType.map { $0.rawValue })
return
}
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 {
if let displayURL = selectedTab?.url?.displayURL {
return displayURL
}
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.
if let url = selectedTab?.url?.displayURL, url.isReaderModeURL {
return url.decodeReaderModeURL
}
return selectedTab?.url?.displayURL ?? selectedURL
}
}
func activityViewController(_ activityViewController: UIActivityViewController, dataTypeIdentifierForActivityType activityType: UIActivityType?) -> String {
//for these customDataID's load the default public.url because they don't seem to work properly with the 1Password UTI.
if let type = activityType, customDataTypeIdentifers.contains(type.rawValue) {
return "public.url"
}
// Because of our UTI declaration, this UTI now satisfies both the 1Password Extension and the usual NSURL for Share extensions.
return "org.appextension.fill-browser-action"
}
}
private extension ShareExtensionHelper {
static func isPasswordManagerExtensionAvailable() -> Bool {
return OnePasswordExtension.shared().isAppExtensionAvailable()
}
func isPasswordManagerActivityType(_ activityType: String?) -> Bool {
if !ShareExtensionHelper.isPasswordManagerExtensionAvailable() {
return false
}
// 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
}
if selectedWebView.url?.absoluteString == nil {
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").")
}
})
}
}