blob: 26bb2710a4836c007013cba2f2f94d3fefbaa9b1 [file] [log] [blame]
/*
* Copyright (C) 2020 Apple Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
* THE POSSIBILITY OF SUCH DAMAGE.
*/
import WebKit
import SwiftUI
extension View {
/// Provides a block to be executed on navigation milestones, and allows for customized handling
/// of navigation policy.
///
/// When providing a custom policy decider you **must** ensure that the coresponding decider's
/// completion method is invoked exactly once. Failure to do so will result in a
/// runtime exception.
///
/// - Parameters:
/// - actionDecider: Decides whether to allow or cancel a navigation.
public func webViewNavigationPolicy(
onAction actionDecider: @escaping (NavigationAction, WebViewState) -> Void
) -> some View {
environment(\.navigationActionDecider, actionDecider)
}
/// Provides a block to be executed on navigation milestones, and allows for customized handling
/// of navigation policy.
///
/// When providing a custom policy decider you **must** ensure that the coresponding decider's
/// completion method is invoked exactly once. Failure to do so will result in a
/// runtime exception.
///
/// - Parameters:
/// - responseDecider: Decides whether to allow or cancel a navigation, after its response
/// is known.
public func webViewNavigationPolicy(
onResponse responseDecider: @escaping (NavigationResponse, WebViewState) -> Void
) -> some View {
environment(\.navigationResponseDecider, responseDecider)
}
/// Provides a block to be executed on navigation milestones, and allows for customized handling
/// of navigation policy.
///
/// When providing a custom policy decider you **must** ensure that the coresponding decider's
/// completion method is invoked exactly once. Failure to do so will result in a
/// runtime exception.
///
/// - Parameters:
/// - actionDecider: Decides whether to allow or cancel a navigation.
/// - responseDecider: Decides whether to allow or cancel a navigation, after its response
/// is known.
public func webViewNavigationPolicy(
onAction actionDecider: @escaping (NavigationAction, WebViewState) -> Void,
onResponse responseDecider: @escaping (NavigationResponse, WebViewState) -> Void
) -> some View {
environment(\.navigationActionDecider, actionDecider)
.environment(\.navigationResponseDecider, responseDecider)
}
}
/// Contains information about an action that may cause a navigation, used for making
/// policy decisions.
@dynamicMemberLookup
public struct NavigationAction {
public typealias Policy = WKNavigationActionPolicy
private var action: WKNavigationAction
private var reply: (WKNavigationActionPolicy, WKWebpagePreferences) -> Void
/// The default set of webpage preferences. This may be changed by passing modified preferences
/// to `decidePolicy(_:webpagePreferences:)`.
public private(set) var webpagePreferences: WKWebpagePreferences
// FIXME: `webpagePreferences` lacks value semantics, which breaks the API contract. This
// instance should not be mutable, but that would require an Objective-C type bridge. We could
// also conform it to `NSCopying` and mark this property as `@NSCopying`.
init(
_ action: WKNavigationAction,
webpagePreferences: WKWebpagePreferences,
reply: @escaping (WKNavigationActionPolicy, WKWebpagePreferences) -> Void
) {
self.action = action
self.reply = reply
self.webpagePreferences = webpagePreferences
}
/// Decides what action to take for this navigation. This method **must** be invoked exactly
/// once, otherwise WebKit will raise an exception.
/// - Parameters:
/// - policy: The desired policy for this navigation.
/// - webpagePreferences: The preferences to use for this navigation, or `nil` to use the
/// default preferences.
public func decidePolicy(_ policy: Policy, webpagePreferences: WKWebpagePreferences? = nil) {
reply(policy, webpagePreferences ?? self.webpagePreferences)
}
public subscript<T>(dynamicMember keyPath: KeyPath<WKNavigationAction, T>) -> T {
action[keyPath: keyPath]
}
fileprivate struct DeciderKey : EnvironmentKey {
static let defaultValue: ((NavigationAction, WebViewState) -> Void)? = nil
}
}
/// Contains information about a navigation response, used for making policy decisions.
@dynamicMemberLookup
public struct NavigationResponse {
public typealias Policy = WKNavigationResponsePolicy
private var response: WKNavigationResponse
private var reply: (Policy) -> Void
init(_ response: WKNavigationResponse, reply: @escaping (Policy) -> Void) {
self.response = response
self.reply = reply
}
/// Decides what action to take for this navigation. This method **must** be invoked exactly
/// once, otherwise WebKit will raise an exception.
/// - Parameters:
/// - policy: The desired policy for this navigation.
public func decidePolicy(_ policy: Policy) {
reply(policy)
}
public subscript<T>(dynamicMember keyPath: KeyPath<WKNavigationResponse, T>) -> T {
response[keyPath: keyPath]
}
fileprivate struct DeciderKey : EnvironmentKey {
static let defaultValue: ((NavigationResponse, WebViewState) -> Void)? = nil
}
}
extension EnvironmentValues {
var navigationActionDecider: ((NavigationAction, WebViewState) -> Void)? {
get { self[NavigationAction.DeciderKey.self] }
set { self[NavigationAction.DeciderKey.self] = newValue }
}
var navigationResponseDecider: ((NavigationResponse, WebViewState) -> Void)? {
get { self[NavigationResponse.DeciderKey.self] }
set { self[NavigationResponse.DeciderKey.self] = newValue }
}
}