Dactyloidae/mobile/ios/Client/Helpers/TabEventHandler.swift
2026-06-26 21:04:09 -07:00

178 lines
5.8 KiB
Swift
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/* 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 Storage
/**
* A handler can be a plain old swift object. It does not need to extend any
* other object, but can.
*
* Handlers should register for tab events with the `registerFor` method, and
* cleanup with the `unregister` method.
*
* ```
* class HandoffHandler {
* var tabObservers: TabObservers!
*
* init() {
* tabObservers = registerFor(.didLoadFavicon, .didLoadPageMetadata)
* }
*
* deinit {
* unregister(tabObservers)
* }
* }
* ```
*
* Handlers can implement any or all `TabEventHandler` methods. If you
* implement a method, you should probably `registerFor` the event above.
*
* ```
* extension HandoffHandler: TabEventHandler {
* func tab(_ tab: Tab, didLoadPageMetadata metadata: PageMetadata) {
* print("\(tab) has \(pageMetadata)")
* }
*
* func tab(_ tab: Tab, didLoadFavicon favicon: Favicon) {
* print("\(tab) has \(favicon)")
* }
* }
* ```
*
* Tab events should probably be only posted from one place, to avoid cycles.
*
* ```
* TabEvent.post(.didLoadPageMetadata(aPageMetadata), for: tab)
* ```
*
* In this manner, we are able to use the notification center and have type safety.
*
*/
// As we want more events we add more here.
// Each event needs:
// 1. a method in the TabEventHandler.
// 2. a default implementation of the method so not everyone needs to implement it
// 3. a TabEventLabel, which is needed for registration
// 4. a TabEvent, with whatever parameters are needed.
// i) a case to map the event to the event label (var label)
// ii) a case to map the event to the event handler (func handle:with:)
protocol TabEventHandler {
func tab(_ tab: Tab, didChangeURL url: URL)
func tab(_ tab: Tab, didLoadPageMetadata metadata: PageMetadata)
func tab(_ tab: Tab, didLoadFavicon favicon: Favicon?, with: Data?)
func tabDidGainFocus(_ tab: Tab)
func tabDidLoseFocus(_ tab: Tab)
func tabDidClose(_ tab: Tab)
}
// Provide default implmentations, because we don't want to litter the code with
// empty methods, and `@objc optional` doesn't really work very well.
extension TabEventHandler {
func tab(_ tab: Tab, didChangeURL url: URL) {}
func tab(_ tab: Tab, didLoadPageMetadata metadata: PageMetadata) {}
func tab(_ tab: Tab, didLoadFavicon favicon: Favicon?, with: Data?) {}
func tabDidGainFocus(_ tab: Tab) {}
func tabDidLoseFocus(_ tab: Tab) {}
func tabDidClose(_ tab: Tab) {}
}
enum TabEventLabel: String {
case didChangeURL
case didLoadPageMetadata
case didLoadFavicon
case didGainFocus
case didLoseFocus
case didClose
}
enum TabEvent {
case didChangeURL(URL)
case didLoadPageMetadata(PageMetadata)
case didLoadFavicon(Favicon?, with: Data?)
case didGainFocus
case didLoseFocus
case didClose
var label: TabEventLabel {
switch self {
case .didChangeURL:
return .didChangeURL
case .didLoadPageMetadata:
return .didLoadPageMetadata
case .didLoadFavicon:
return .didLoadFavicon
case .didGainFocus:
return .didGainFocus
case .didLoseFocus:
return .didLoseFocus
case .didClose:
return .didClose
}
}
func handle(_ tab: Tab, with handler: TabEventHandler) {
switch self {
case .didChangeURL(let url):
handler.tab(tab, didChangeURL: url)
case .didLoadPageMetadata(let metadata):
handler.tab(tab, didLoadPageMetadata: metadata)
case .didLoadFavicon(let favicon, let data):
handler.tab(tab, didLoadFavicon: favicon, with: data)
case .didGainFocus:
handler.tabDidGainFocus(tab)
case .didLoseFocus:
handler.tabDidLoseFocus(tab)
case .didClose:
handler.tabDidClose(tab)
}
}
}
// Hide some of the machinery away from the boiler plate above.
////////////////////////////////////////////////////////////////////////////////////////
extension TabEventLabel {
var name: Notification.Name {
return Notification.Name(self.rawValue)
}
}
extension TabEvent {
func notification(for tab: Any) -> Notification {
return Notification(name: label.name, object: tab, userInfo: ["payload": self])
}
/// Use this method to post notifications to any concerned listeners.
static func post(_ event: TabEvent, for tab: Any) {
center.post(event.notification(for: tab))
}
}
// These methods are used by TabEventHandler implementers.
// Their usage remains consistent, even as we add more event types and handler methods.
////////////////////////////////////////////////////////////////////////////////////////
private let center = NotificationCenter()
typealias TabObservers = [NSObjectProtocol]
extension TabEventHandler {
/// Implementations of handles should use this method to register for events.
/// `TabObservers` should be preserved for unregistering later.
func registerFor(_ tabEvents: TabEventLabel..., queue: OperationQueue? = nil) -> TabObservers {
return tabEvents.map { eventType in
center.addObserver(forName: eventType.name, object: nil, queue: queue) { notification in
guard let tab = notification.object as? Tab,
let event = notification.userInfo?["payload"] as? TabEvent else {
return
}
event.handle(tab, with: self)
}
}
}
func unregister(_ observers: TabObservers) {
observers.forEach { observer in
center.removeObserver(observer)
}
}
}