mirror of
https://repo.dactyloidae.xyz/Dactyloidae/UXP.git
synced 2026-08-23 04:43:07 +09:00
178 lines
5.8 KiB
Swift
178 lines
5.8 KiB
Swift
/* 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)
|
||
}
|
||
}
|
||
}
|