tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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 }