EventDispatcher.swift (4627B)
1 // This Source Code Form is subject to the terms of the Mozilla Public 2 // License, v. 2.0. If a copy of the MPL was not distributed with this 3 // file, You can obtain one at http://mozilla.org/MPL/2.0/. 4 5 /// Error type thrown by `EventListener` callbacks 6 struct HandlerError: Error { 7 let value: Any? 8 9 init(_ value: Any?) { 10 self.value = value 11 } 12 } 13 14 /// Protocol implemented by objects which want to listen to messages from `EventDispatcher`. 15 protocol EventListener { 16 /// Handler method for a message dispatched on the `EventDispatcher` by Gecko. 17 @MainActor 18 func handleMessage(type: String, message: [String: Any?]?) async throws -> Any? 19 } 20 21 extension EventListener { 22 /// Internal helper for invoking the callback from a synchronous context. 23 func handleMessage(type: String, message: [String: Any?]?, callback: EventCallback?) { 24 Task { @MainActor in 25 do { 26 let result = try await self.handleMessage(type: type, message: message) 27 callback?.sendSuccess(result) 28 } catch let error as HandlerError { 29 callback?.sendError(error.value) 30 } catch { 31 callback?.sendError("\(error)") 32 } 33 } 34 } 35 } 36 37 class EventDispatcher: NSObject, SwiftEventDispatcher { 38 static var runtimeInstance = EventDispatcher() 39 static var dispatchers: [String: EventDispatcher] = [:] 40 41 struct QueuedMessage { 42 let type: String 43 let message: [String: Any?]? 44 let callback: EventCallback? 45 } 46 47 var gecko: GeckoEventDispatcher? 48 var queue: [QueuedMessage]? = [] 49 var listeners: [String: [EventListener]] = [:] 50 var name: String? 51 52 override init() {} 53 54 init(name: String) { 55 self.name = name 56 } 57 58 public static func lookup(byName: String) -> EventDispatcher { 59 if let dispatcher = dispatchers[byName] { 60 return dispatcher 61 } 62 let newDispatcher = EventDispatcher(name: byName) 63 dispatchers[byName] = newDispatcher 64 return newDispatcher 65 } 66 67 public func addListener(type: String, listener: EventListener) { 68 listeners[type, default: []] += [listener] 69 } 70 71 public func dispatch( 72 type: String, message: [String: Any?]? = nil, callback: EventCallback? = nil 73 ) { 74 if let eventListeners = listeners[type] { 75 for listener in eventListeners { 76 listener.handleMessage(type: type, message: message, callback: callback) 77 } 78 } else if queue != nil { 79 queue!.append(QueuedMessage(type: type, message: message, callback: callback)) 80 } else { 81 gecko?.dispatch(toGecko: type, message: message, callback: callback) 82 } 83 } 84 85 public func query(type: String, message: [String: Any?]? = nil) async throws -> Any? { 86 class AsyncCallback: NSObject, EventCallback { 87 var continuation: CheckedContinuation<Any?, Error>? 88 init(_ continuation: CheckedContinuation<Any?, Error>) { 89 self.continuation = continuation 90 } 91 func sendSuccess(_ response: Any?) { 92 continuation?.resume(returning: response) 93 continuation = nil 94 } 95 func sendError(_ response: Any?) { 96 continuation?.resume(throwing: HandlerError(response)) 97 continuation = nil 98 } 99 deinit { 100 continuation?.resume(throwing: HandlerError("callback never invoked")) 101 continuation = nil 102 } 103 } 104 105 return try await withCheckedThrowingContinuation({ 106 dispatch(type: type, message: message, callback: AsyncCallback($0)) 107 }) 108 } 109 110 func attach(_ dispatcher: GeckoEventDispatcher?) { 111 gecko = dispatcher 112 } 113 114 func dispatch(toSwift type: String, message: Any?, callback: EventCallback?) { 115 let message = message as! [String: Any?]? 116 if let eventListeners = listeners[type] { 117 for listener in eventListeners { 118 listener.handleMessage(type: type, message: message, callback: callback) 119 } 120 } 121 } 122 123 func activate() { 124 // Drain the queue, then clear it out so future messages are dispatched 125 // directly. 126 if let queue = self.queue { 127 self.queue = nil 128 for event in queue { 129 gecko?.dispatch(toGecko: event.type, message: event.message, callback: event.callback) 130 } 131 } 132 } 133 134 func hasListener(_ type: String) -> Bool { 135 listeners.keys.contains(type) 136 } 137 }