File: NavigationDelegate.swift

package info (click to toggle)
firefox 147.0.3-1
  • links: PTS, VCS
  • area: main
  • in suites: sid
  • size: 4,683,320 kB
  • sloc: cpp: 7,607,359; javascript: 6,533,295; ansic: 3,775,223; python: 1,415,500; xml: 634,561; asm: 438,949; java: 186,241; sh: 62,752; makefile: 18,079; objc: 13,092; perl: 12,808; yacc: 4,583; cs: 3,846; pascal: 3,448; lex: 1,720; ruby: 1,003; php: 436; lisp: 258; awk: 247; sql: 66; sed: 54; csh: 10; exp: 6
file content (163 lines) | stat: -rw-r--r-- 7,039 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
// 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/.

public enum LoadRequestTarget {
    /// The load is targeted to complete within the current `GeckoSession`.
    case current
    /// The load is targeted to complete within a new `GeckoSession`.
    case new
}

public struct LoadRequest {
    /// The URI to be loaded.
    public let uri: String

    /// The URI of the origin page that triggered the load request.
    ///
    /// `nil` for initial loads, and loads originating from `data:` URIs.
    public let triggerUri: String?

    /// The target where the window has requested to open.
    public let target: LoadRequestTarget

    /// True if and only if the request was triggered by an HTTP redirect.
    ///
    /// If the user loads URI "a", which redirects to URI "b", then
    /// `onLoadRequest` will be called twice, first with uri "a" and `isRedirect
    /// = false`, then with uri "b" and `isRedirect = true`.
    public let isRedirect: Bool

    /// True if there was an active user gesture when the load was requested.
    public let hasUserGesture: Bool

    /// This load request was initiated by a direct navigation from the
    /// application. E.g. when calling `GeckoSession.load`.
    public let isDirectNavigation: Bool
}

public protocol NavigationDelegate {
    /// A view has started loading content from the network.
    func onLocationChange(session: GeckoSession, url: String?, permissions: [ContentPermission])

    /// The view's ability to go back has changed.
    func onCanGoBack(session: GeckoSession, canGoBack: Bool)

    /// The view's ability to go forward has changed.
    func onCanGoForward(session: GeckoSession, canGoForward: Bool)

    /// A request to open an URI. This is called before each top-level page load
    /// to allow custom behavior. For example, this can be used to override the
    /// behavior of TAGET_WINDOW_NEW requests, which defaults to requesting a
    /// new GeckoSession via onNewSession.
    ///
    /// Returns an `AllowOrDeny` which indicates whether or not the load was
    /// handled. If unhandled, Gecko will continue the load as normal. If
    /// handled (a `deny` value), Gecko will abandon the load.
    func onLoadRequest(session: GeckoSession, request: LoadRequest) async -> AllowOrDeny

    /// A request to load a URI in a non-top-level context.
    ///
    /// Returns an `AllowOrDeny` which indicates whether or not the load was
    /// handled. If unhandled, Gecko will continue the load as normal. If
    /// handled (a `deny` value), Gecko will abandon the load.
    func onSubframeLoadRequest(session: GeckoSession, request: LoadRequest) async -> AllowOrDeny

    /// A request has been made to open a new session. The URI is provided only for informational
    /// purposes. Do not call GeckoSession.load here. Additionally, the returned GeckoSession must be
    /// a newly-created one.
    ///
    /// If nil is returned, the request for the request for a new window by web
    /// content will fail. e.g., `window.open()` will return null. The
    /// implementation of onNewSession is responsible for maintaining a
    /// reference to the returned object, to prevent it from being destroyed.
    func onNewSession(session: GeckoSession, uri: String) async -> GeckoSession?

    /// A load error has occurred.
    ///
    /// The returned string is a URI to display as an error. Returning `nil`
    /// will halt the load entirely.
    ///
    /// The following special methods are made available to the URI:
    ///
    /// - document.addCertException(isTemporary), returns Promise
    /// - document.getFailedCertSecurityInfo(), returns FailedCertSecurityInfo
    /// - document.getNetErrorInfo(), returns NetErrorInfo
    /// - document.reloadWithHttpsOnlyException()
    // FIXME: Implement onLoadError & WebRequestError
    // func onLoadError(session: GeckoSession, uri: String?, error: WebRequestError) -> String?
}

enum NavigationEvents: String, CaseIterable {
    case locationChange = "GeckoView:LocationChange"
    case onNewSession = "GeckoView:OnNewSession"
    case onLoadError = "GeckoView:OnLoadError"
    case onLoadRequest = "GeckoView:OnLoadRequest"
}

func newNavigationHandler(_ session: GeckoSession) -> GeckoSessionHandler<
    NavigationDelegate, NavigationEvents
> {
    GeckoSessionHandler(moduleName: "GeckoViewNavigation", session: session) {
        @MainActor session, delegate, event, message in
        switch event {
        case .locationChange:
            if message!["isTopLevel"] as! Bool {
                let permissions = message!["permissions"] as? [[String: Any?]]
                delegate?.onLocationChange(
                    session: session,
                    url: message!["uri"] as? String,
                    permissions: permissions?.map(ContentPermission.fromDictionary) ?? [])
            }
            delegate?.onCanGoBack(session: session, canGoBack: message!["canGoBack"] as! Bool)
            delegate?.onCanGoForward(
                session: session, canGoForward: message!["canGoForward"] as! Bool)
            return nil

        case .onNewSession:
            let newSessionId = message!["newSessionId"] as! String
            if let result = await delegate?.onNewSession(
                session: session, uri: message!["uri"] as! String)
            {
                assert(result.isOpen())
                result.open(windowId: newSessionId)
                return true
            } else {
                return false
            }

        case .onLoadError:
            let uri = message!["uri"] as! String
            let errorCode = message!["error"] as! Int64
            let errorModule = message!["errorModule"] as! Int32
            let errorClass = message!["errorClass"] as! Int32
            return nil

        case .onLoadRequest:
            func convertTarget(_ target: Int32) -> LoadRequestTarget {
                switch target {
                case 0:  // OPEN_DEFAULTWINDOW
                    return .current
                case 1:  // OPEN_CURRENTWINDOW
                    return .current
                default:  // OPEN_NEWWINDOW, OPEN_NEWTAB
                    return .new
                }
            }

            // Match with nsIWebNavigation.idl.
            let LOAD_REQUEST_IS_REDIRECT = 0x800000

            let loadRequest = LoadRequest(
                uri: message!["uri"] as! String,
                triggerUri: message!["triggerUri"] as? String,
                target: convertTarget(message!["where"] as! Int32),
                isRedirect: ((message!["flags"] as! Int) & LOAD_REQUEST_IS_REDIRECT) != 0,
                hasUserGesture: message!["hasUserGesture"] as! Bool,
                isDirectNavigation: true)

            let result = await delegate?.onLoadRequest(session: session, request: loadRequest)
            return result == .allow
        }
    }
}