Source code

Revision control

Copy as Markdown

Other Tools

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