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
}
}
}
|