-
Notifications
You must be signed in to change notification settings - Fork 3k
/
Copy pathSceneDelegate.swift
242 lines (193 loc) · 9.36 KB
/
SceneDelegate.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
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
// 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 UIKit
import CoreSpotlight
import Storage
import Shared
import Sync
import UserNotifications
import Account
import MozillaAppServices
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
var window: UIWindow?
/// This is temporary. We don't want to continue treating App / Scene delegates as containers for certain session specific properties.
/// TODO: When we begin to support multiple scenes, this is risky to keep. If we foregroundBVC, we should have a more specific
/// way to foreground the BVC FOR the scene being actively interacted with.
var browserViewController: BrowserViewController!
let profile: Profile = AppContainer.shared.resolve()
let tabManager: TabManager = AppContainer.shared.resolve()
// MARK: - Connecting / Disconnecting Scenes
/// Invoked when the app creates OR restores an instance of the UI.
///
/// Use this method to respond to the addition of a new scene, and begin loading data that needs to display.
/// Take advantage of what's given in `options`.
func scene(
_ scene: UIScene,
willConnectTo session: UISceneSession,
options connectionOptions: UIScene.ConnectionOptions
) {
guard !AppConstants.isRunningUnitTest else { return }
let window = configureWindowFor(scene)
let rootVC = configureRootViewController()
window.rootViewController = rootVC
window.makeKeyAndVisible()
self.window = window
handleDeeplinkOrShortcutsAtLaunch(with: connectionOptions, on: scene)
}
// MARK: - Transitioning to Foreground
/// Invoked when the interface is finished loading for your screen, but before that interface appears on screen.
///
/// Use this method to refresh the contents of your scene's view (especially if it's a restored scene), or other activities that need
/// to begin.
func sceneDidBecomeActive(_ scene: UIScene) {
guard !AppConstants.isRunningUnitTest else { return }
/// Resume previously stopped downloads for, and on, THIS scene only.
browserViewController.downloadQueue.resumeAll()
}
// MARK: - Transitioning to Background
/// The scene's running in the background and not visible on screen.
///
/// Use this method to reduce the scene's memory usage, clear claims to resources & dependencies / services.
/// UIKit takes a snapshot of the scene for the app switcher after this method returns.
func sceneDidEnterBackground(_ scene: UIScene) {
browserViewController.downloadQueue.pauseAll()
}
// MARK: - Opening URLs
/// Asks the delegate to open one or more URLs.
///
/// This method is equivalent to AppDelegate's openURL method. We implement deep links this way.
func scene(
_ scene: UIScene,
openURLContexts URLContexts: Set<UIOpenURLContext>
) {
guard let url = URLContexts.first?.url,
let routerPath = NavigationPath(url: url) else { return }
if profile.prefs.boolForKey(PrefsKeys.AppExtensionTelemetryOpenUrl) != nil {
profile.prefs.removeObjectForKey(PrefsKeys.AppExtensionTelemetryOpenUrl)
var object = TelemetryWrapper.EventObject.url
if case .text = routerPath {
object = .searchText
}
TelemetryWrapper.recordEvent(category: .appExtensionAction, method: .applicationOpenUrl, object: object)
}
DispatchQueue.main.async {
NavigationPath.handle(nav: routerPath, with: self.browserViewController)
}
}
// MARK: - Continuing User Activities
/// Use this method to handle Handoff-related data or other activities.
func scene(_ scene: UIScene, continue userActivity: NSUserActivity) {
if userActivity.activityType == SiriShortcuts.activityType.openURL.rawValue {
browserViewController.openBlankNewTab(focusLocationField: false)
}
// If the `NSUserActivity` has a `webpageURL`, it is either a deep link or an old history item
// reached via a "Spotlight" search before we began indexing visited pages via CoreSpotlight.
if let url = userActivity.webpageURL {
let query = url.getQuery()
// Check for fxa sign-in code and launch the login screen directly
if query["signin"] != nil {
// bvc.launchFxAFromDeeplinkURL(url) // Was using Adjust. Consider hooking up again when replacement system in-place.
}
// Per Adjust documentation, https://docs.adjust.com/en/universal-links/#running-campaigns-through-universal-links,
// it is recommended that links contain the `deep_link` query parameter. This link will also
// be url encoded.
if let deepLink = query["deep_link"]?.removingPercentEncoding, let url = URL(string: deepLink) {
browserViewController.switchToTabForURLOrOpen(url)
}
browserViewController.switchToTabForURLOrOpen(url)
}
// Otherwise, check if the `NSUserActivity` is a CoreSpotlight item and switch to its tab or
// open a new one.
if userActivity.activityType == CSSearchableItemActionType {
if let userInfo = userActivity.userInfo,
let urlString = userInfo[CSSearchableItemActivityIdentifier] as? String,
let url = URL(string: urlString) {
browserViewController.switchToTabForURLOrOpen(url)
}
}
}
// MARK: - Performing Tasks
/// Use this method to handle a selected shortcut action.
///
/// Invoked when:
/// - a user activates the application by selecting a shortcut item on the home screen AND
/// - the window scene is already connected.
func windowScene(
_ windowScene: UIWindowScene,
performActionFor shortcutItem: UIApplicationShortcutItem,
completionHandler: @escaping (Bool) -> Void
) {
QuickActionsImplementation().handleShortCutItem(
shortcutItem,
withBrowserViewController: browserViewController,
completionHandler: completionHandler
)
}
// MARK: - Misc. Helpers
private func configureWindowFor(_ scene: UIScene) -> UIWindow {
guard let windowScene = (scene as? UIWindowScene) else {
return UIWindow(frame: UIScreen.main.bounds)
}
if #available(iOS 14, *) {
windowScene.screenshotService?.delegate = self
}
let window = UIWindow(windowScene: windowScene)
if !LegacyThemeManager.instance.systemThemeIsOn {
window.overrideUserInterfaceStyle = LegacyThemeManager.instance.userInterfaceStyle
}
return window
}
private func configureRootViewController() -> UINavigationController {
let browserViewController = BrowserViewController(profile: profile, tabManager: tabManager)
// TODO: When we begin to support multiple scenes, remove this line and the reference to BVC in SceneDelegate.
self.browserViewController = browserViewController
let navigationController = UINavigationController(rootViewController: browserViewController)
navigationController.isNavigationBarHidden = true
navigationController.edgesForExtendedLayout = UIRectEdge(rawValue: 0)
return navigationController
}
/// Handling either deeplinks or shortcuts at launch is slightly different than when the scene has been backgrounded.
private func handleDeeplinkOrShortcutsAtLaunch(
with connectionOptions: UIScene.ConnectionOptions,
on scene: UIScene
) {
/// Handling deeplinks at launch can be handled this way.
if !connectionOptions.urlContexts.isEmpty {
self.scene(scene, openURLContexts: connectionOptions.urlContexts)
}
/// At launch, shortcut items can be handled this way.
if let shortcutItem = connectionOptions.shortcutItem {
QuickActionsImplementation().handleShortCutItem(
shortcutItem,
withBrowserViewController: browserViewController,
completionHandler: { _ in }
)
}
}
}
@available(iOS 14, *)
extension SceneDelegate: UIScreenshotServiceDelegate {
func screenshotService(_ screenshotService: UIScreenshotService,
generatePDFRepresentationWithCompletion completionHandler: @escaping (Data?, Int, CGRect) -> Void) {
guard browserViewController.homepageViewController?.view.alpha != 1,
browserViewController.presentedViewController == nil,
let webView = tabManager.selectedTab?.currentWebView(),
let url = webView.url,
InternalURL(url) == nil else {
completionHandler(nil, 0, .zero)
return
}
var rect = webView.scrollView.frame
rect.origin.x = webView.scrollView.contentOffset.x
rect.origin.y = webView.scrollView.contentSize.height - rect.height - webView.scrollView.contentOffset.y
webView.createPDF { result in
switch result {
case .success(let data):
completionHandler(data, 0, rect)
case .failure:
completionHandler(nil, 0, .zero)
}
}
}
}