tor-browser

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

NavigationDelegate.swift (7039B)


      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 public enum LoadRequestTarget {
      6    /// The load is targeted to complete within the current `GeckoSession`.
      7    case current
      8    /// The load is targeted to complete within a new `GeckoSession`.
      9    case new
     10 }
     11 
     12 public struct LoadRequest {
     13    /// The URI to be loaded.
     14    public let uri: String
     15 
     16    /// The URI of the origin page that triggered the load request.
     17    ///
     18    /// `nil` for initial loads, and loads originating from `data:` URIs.
     19    public let triggerUri: String?
     20 
     21    /// The target where the window has requested to open.
     22    public let target: LoadRequestTarget
     23 
     24    /// True if and only if the request was triggered by an HTTP redirect.
     25    ///
     26    /// If the user loads URI "a", which redirects to URI "b", then
     27    /// `onLoadRequest` will be called twice, first with uri "a" and `isRedirect
     28    /// = false`, then with uri "b" and `isRedirect = true`.
     29    public let isRedirect: Bool
     30 
     31    /// True if there was an active user gesture when the load was requested.
     32    public let hasUserGesture: Bool
     33 
     34    /// This load request was initiated by a direct navigation from the
     35    /// application. E.g. when calling `GeckoSession.load`.
     36    public let isDirectNavigation: Bool
     37 }
     38 
     39 public protocol NavigationDelegate {
     40    /// A view has started loading content from the network.
     41    func onLocationChange(session: GeckoSession, url: String?, permissions: [ContentPermission])
     42 
     43    /// The view's ability to go back has changed.
     44    func onCanGoBack(session: GeckoSession, canGoBack: Bool)
     45 
     46    /// The view's ability to go forward has changed.
     47    func onCanGoForward(session: GeckoSession, canGoForward: Bool)
     48 
     49    /// A request to open an URI. This is called before each top-level page load
     50    /// to allow custom behavior. For example, this can be used to override the
     51    /// behavior of TAGET_WINDOW_NEW requests, which defaults to requesting a
     52    /// new GeckoSession via onNewSession.
     53    ///
     54    /// Returns an `AllowOrDeny` which indicates whether or not the load was
     55    /// handled. If unhandled, Gecko will continue the load as normal. If
     56    /// handled (a `deny` value), Gecko will abandon the load.
     57    func onLoadRequest(session: GeckoSession, request: LoadRequest) async -> AllowOrDeny
     58 
     59    /// A request to load a URI in a non-top-level context.
     60    ///
     61    /// Returns an `AllowOrDeny` which indicates whether or not the load was
     62    /// handled. If unhandled, Gecko will continue the load as normal. If
     63    /// handled (a `deny` value), Gecko will abandon the load.
     64    func onSubframeLoadRequest(session: GeckoSession, request: LoadRequest) async -> AllowOrDeny
     65 
     66    /// A request has been made to open a new session. The URI is provided only for informational
     67    /// purposes. Do not call GeckoSession.load here. Additionally, the returned GeckoSession must be
     68    /// a newly-created one.
     69    ///
     70    /// If nil is returned, the request for the request for a new window by web
     71    /// content will fail. e.g., `window.open()` will return null. The
     72    /// implementation of onNewSession is responsible for maintaining a
     73    /// reference to the returned object, to prevent it from being destroyed.
     74    func onNewSession(session: GeckoSession, uri: String) async -> GeckoSession?
     75 
     76    /// A load error has occurred.
     77    ///
     78    /// The returned string is a URI to display as an error. Returning `nil`
     79    /// will halt the load entirely.
     80    ///
     81    /// The following special methods are made available to the URI:
     82    ///
     83    /// - document.addCertException(isTemporary), returns Promise
     84    /// - document.getFailedCertSecurityInfo(), returns FailedCertSecurityInfo
     85    /// - document.getNetErrorInfo(), returns NetErrorInfo
     86    /// - document.reloadWithHttpsOnlyException()
     87    // FIXME: Implement onLoadError & WebRequestError
     88    // func onLoadError(session: GeckoSession, uri: String?, error: WebRequestError) -> String?
     89 }
     90 
     91 enum NavigationEvents: String, CaseIterable {
     92    case locationChange = "GeckoView:LocationChange"
     93    case onNewSession = "GeckoView:OnNewSession"
     94    case onLoadError = "GeckoView:OnLoadError"
     95    case onLoadRequest = "GeckoView:OnLoadRequest"
     96 }
     97 
     98 func newNavigationHandler(_ session: GeckoSession) -> GeckoSessionHandler<
     99    NavigationDelegate, NavigationEvents
    100 > {
    101    GeckoSessionHandler(moduleName: "GeckoViewNavigation", session: session) {
    102        @MainActor session, delegate, event, message in
    103        switch event {
    104        case .locationChange:
    105            if message!["isTopLevel"] as! Bool {
    106                let permissions = message!["permissions"] as? [[String: Any?]]
    107                delegate?.onLocationChange(
    108                    session: session,
    109                    url: message!["uri"] as? String,
    110                    permissions: permissions?.map(ContentPermission.fromDictionary) ?? [])
    111            }
    112            delegate?.onCanGoBack(session: session, canGoBack: message!["canGoBack"] as! Bool)
    113            delegate?.onCanGoForward(
    114                session: session, canGoForward: message!["canGoForward"] as! Bool)
    115            return nil
    116 
    117        case .onNewSession:
    118            let newSessionId = message!["newSessionId"] as! String
    119            if let result = await delegate?.onNewSession(
    120                session: session, uri: message!["uri"] as! String)
    121            {
    122                assert(result.isOpen())
    123                result.open(windowId: newSessionId)
    124                return true
    125            } else {
    126                return false
    127            }
    128 
    129        case .onLoadError:
    130            let uri = message!["uri"] as! String
    131            let errorCode = message!["error"] as! Int64
    132            let errorModule = message!["errorModule"] as! Int32
    133            let errorClass = message!["errorClass"] as! Int32
    134            return nil
    135 
    136        case .onLoadRequest:
    137            func convertTarget(_ target: Int32) -> LoadRequestTarget {
    138                switch target {
    139                case 0:  // OPEN_DEFAULTWINDOW
    140                    return .current
    141                case 1:  // OPEN_CURRENTWINDOW
    142                    return .current
    143                default:  // OPEN_NEWWINDOW, OPEN_NEWTAB
    144                    return .new
    145                }
    146            }
    147 
    148            // Match with nsIWebNavigation.idl.
    149            let LOAD_REQUEST_IS_REDIRECT = 0x800000
    150 
    151            let loadRequest = LoadRequest(
    152                uri: message!["uri"] as! String,
    153                triggerUri: message!["triggerUri"] as? String,
    154                target: convertTarget(message!["where"] as! Int32),
    155                isRedirect: ((message!["flags"] as! Int) & LOAD_REQUEST_IS_REDIRECT) != 0,
    156                hasUserGesture: message!["hasUserGesture"] as! Bool,
    157                isDirectNavigation: true)
    158 
    159            let result = await delegate?.onLoadRequest(session: session, request: loadRequest)
    160            return result == .allow
    161        }
    162    }
    163 }