Skip to content

Commit

Permalink
No commit message
Browse files Browse the repository at this point in the history
  • Loading branch information
marcprux committed Apr 3, 2024
1 parent f2943d7 commit f61fce3
Show file tree
Hide file tree
Showing 4 changed files with 120 additions and 50 deletions.
11 changes: 5 additions & 6 deletions Sources/SkipWeb/SkipWeb.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,6 @@ import OSLog

let logger: Logger = Logger(subsystem: "SkipWeb", category: "WebView")

let homePage = "https://en.wikipedia.org/wiki/Special:Random" // "https://wikipedia.org"
let homeURL = URL(string: homePage)!

/// A store for persisting `WebBrowser` state such as history, favorites, and preferences.
public protocol WebBrowserStore {
func saveItems(type: PageInfo.PageType, items: [PageInfo]) throws
Expand Down Expand Up @@ -45,21 +42,23 @@ public struct SearchEngine : Identifiable {
public typealias ID = String

public let id: ID
public let homeURL: String
public let name: () -> String
public let queryURL: (String, String) -> String?
public let suggestionURL: (String, String) -> String?

public init(id: String, name: @escaping () -> String, queryURL: @escaping (String, String) -> String?, suggestionURL: @escaping (String, String) -> String?) {
public init(id: String, homeURL: String, name: @escaping () -> String, queryURL: @escaping (String, String) -> String?, suggestionURL: @escaping (String, String) -> String?) {
self.id = id
self.homeURL = homeURL
self.name = name
self.queryURL = queryURL
self.suggestionURL = suggestionURL
}
}


#if !SKIP
extension URL {
#if !SKIP
public func normalizedHost(stripWWWSubdomainOnly: Bool = false) -> String? {
// Use components.host instead of self.host since the former correctly preserves
// brackets for IPv6 hosts, whereas the latter strips them.
Expand Down Expand Up @@ -106,7 +105,7 @@ extension URL {

return self
}
#endif
}
#endif


91 changes: 65 additions & 26 deletions Sources/SkipWeb/WebBrowser.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,12 @@ import SwiftUI

#if SKIP || os(iOS)

let supportTabs = false

/// A complete browser view, including a URL bar, the WebView canvas, and toolbar buttons for common actions.
@available(macOS 14.0, iOS 17.0, *)
@MainActor public struct WebBrowser: View {
@State var viewModel = BrowserViewModel(url: homePage, navigator: WebViewNavigator())
@State var viewModel = BrowserViewModel(navigator: WebViewNavigator())
@State var state = WebViewState()
@State var triggerImpact = false
@State var triggerWarning = false
Expand All @@ -35,15 +37,32 @@ import SwiftUI
}
}

var homeURL: URL? {
if let homePage = URL(string: configuration.searchEngines.first?.homeURL ?? "https://example.org") {
return homePage
} else {
return nil
}

}
public var body: some View {
VStack(spacing: 0.0) {
if urlBarOnTop { URLBar() }
WebView(configuration: configuration, navigator: viewModel.navigator, state: $state)
.frame(maxHeight: .infinity)
if !urlBarOnTop { URLBar() }
ProgressView(value: state.estimatedProgress)
.progressViewStyle(.linear)
.frame(height: 1.0) // thin progress bar
//.tint(state.isLoading ? Color.accentColor : Color.clear)
//.opacity(state.isLoading ? 1.0 : 0.5)
.opacity(1.0 - (state.estimatedProgress ?? 0.0) / 2.0)
}
.task {
viewModel.navigator.load(url: homeURL, newTab: false)
// load the most recent history page when we first start
if let lastPage = (try? store.loadItems(type: PageInfo.PageType.history, ids: []))?.first {
viewModel.navigator.load(url: lastPage.url, newTab: false)
}
}
.sheet(isPresented: $viewModel.showSettings) {
SettingsView()
Expand Down Expand Up @@ -71,11 +90,15 @@ import SwiftUI
ToolbarItemGroup(placement: toolbarPlacement) {
backButton()
Spacer()
tabListButton()
Spacer()
if supportTabs {
tabListButton()
Spacer()
}
moreButton()
Spacer()
newTabButton()
if supportTabs {
Spacer()
newTabButton()
}
Spacer()
forwardButton()
}
Expand Down Expand Up @@ -257,11 +280,24 @@ import SwiftUI
//.font(Font.body)
#if !SKIP
#if os(iOS)
//.keyboardType(.URL)
.keyboardType(.webSearch)
.textContentType(.URL)
.autocorrectionDisabled(true)
.textInputAutocapitalization(.never)
//.toolbar {
// ToolbarItemGroup(placement: .keyboard) {
// Button("Custom Search…") {
// logger.log("Clicked Custom Search…")
// }
// }
//}
//.textScale(Text.Scale.secondary, isEnabled: true)
.onReceive(NotificationCenter.default.publisher(for: UITextField.textDidBeginEditingNotification)) { obj in
logger.log("received textDidBeginEditingNotification: \(obj.object as? NSObject)")
if let textField = obj.object as? UITextField {
textField.selectAll(nil)
}
}
#endif
#endif
.onSubmit(of: .text) {
Expand Down Expand Up @@ -384,7 +420,9 @@ import SwiftUI
func homeAction() {
logger.info("homeAction")
hapticFeedback()
viewModel.navigator.load(url: homeURL, newTab: false)
if let homeURL = homeURL {
viewModel.navigator.load(url: homeURL, newTab: false)
}
}

func backAction() {
Expand Down Expand Up @@ -449,25 +487,27 @@ import SwiftUI

func moreButton() -> some View {
Menu {
Button(action: newTabAction) {
Label {
Text("New Tab", bundle: .module, comment: "more button string for creating a new tab")
} icon: {
Image(systemName: "plus.square.on.square")
if supportTabs {
Button(action: newTabAction) {
Label {
Text("New Tab", bundle: .module, comment: "more button string for creating a new tab")
} icon: {
Image(systemName: "plus.square.on.square")
}
}
}
.accessibilityIdentifier("button.new")
.accessibilityIdentifier("button.new")

Button(action: closeAction) {
Label {
Text("Close Tab", bundle: .module, comment: "more button string for closing a tab")
} icon: {
Image(systemName: "xmark")
Button(action: closeAction) {
Label {
Text("Close Tab", bundle: .module, comment: "more button string for closing a tab")
} icon: {
Image(systemName: "xmark")
}
}
}
.accessibilityIdentifier("button.close")
.accessibilityIdentifier("button.close")

Divider()
Divider()
}

Button(action: reloadAction) {
Label {
Expand Down Expand Up @@ -507,7 +547,7 @@ import SwiftUI
// }

// share button
ShareLink(item: state.pageURL ?? homeURL)
ShareLink(item: state.pageURL ?? URL(string: "https://example.org")!)
.disabled(state.pageURL == nil)

Button(action: favoriteAction) {
Expand Down Expand Up @@ -646,8 +686,7 @@ struct PageInfoList : View {
var showSettings = false
var showHistory = false

public init(url urlTextField: String, navigator: WebViewNavigator) {
self.urlTextField = urlTextField
public init(navigator: WebViewNavigator) {
self.navigator = navigator
}

Expand Down
7 changes: 5 additions & 2 deletions Sources/SkipWeb/WebEngine.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,12 @@ import kotlinx.coroutines.launch
/// The `WebEngine` is used both as the render for a `WebView` and `BrowserView`,
/// and can also be used in a headless context to drive web pages
/// and evaluate JavaScript.
@MainActor public class WebEngine {
@MainActor public class WebEngine : ObservableObject {
public let configuration: WebEngineConfiguration
public let webView: PlatformWebView
#if !SKIP
private var observers: [NSKeyValueObservation] = []
#endif

/// Create a WebEngine with the specified configuration.
/// - Parameters:
Expand All @@ -35,7 +38,7 @@ import kotlinx.coroutines.launch
self.configuration = configuration

#if !SKIP
self.webView = webView ?? PlatformWebView(frame: .zero, configuration: configuration.webViewConfiguration)
self.webView = webView ?? WKWebView(frame: .zero, configuration: configuration.webViewConfiguration)
#else
// fall back to using the global android context if the activity context is not set in the configuration
self.webView = webView ?? PlatformWebView(configuration.context ?? ProcessInfo.processInfo.androidContext)
Expand Down
61 changes: 45 additions & 16 deletions Sources/SkipWeb/WebView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
// as published by the Free Software Foundation https://fsf.org
import SwiftUI
import OSLog
import Combine
#if !SKIP
import WebKit
public typealias PlatformWebView = WKWebView
Expand Down Expand Up @@ -75,6 +76,7 @@ public struct WebView : View {
public internal(set) var isLoading: Bool
public internal(set) var isProvisionallyNavigating: Bool
public internal(set) var pageURL: URL?
public internal(set) var estimatedProgress: Double?
public internal(set) var pageTitle: String?
public internal(set) var pageImageURL: URL?
public internal(set) var pageHTML: String?
Expand All @@ -84,8 +86,9 @@ public struct WebView : View {
public internal(set) var backList: [BackForwardListItem]
public internal(set) var forwardList: [BackForwardListItem]

public init(isLoading: Bool = false, isProvisionallyNavigating: Bool = false, pageURL: URL? = nil, pageTitle: String? = nil, pageImageURL: URL? = nil, pageHTML: String? = nil, error: Error? = nil, canGoBack: Bool = false, canGoForward: Bool = false, backList: [BackForwardListItem] = [], forwardList: [BackForwardListItem] = []) {
public init(isLoading: Bool = false, estimatedProgress: Double? = nil, isProvisionallyNavigating: Bool = false, pageURL: URL? = nil, pageTitle: String? = nil, pageImageURL: URL? = nil, pageHTML: String? = nil, error: Error? = nil, canGoBack: Bool = false, canGoForward: Bool = false, backList: [BackForwardListItem] = [], forwardList: [BackForwardListItem] = []) {
self.isLoading = isLoading
self.estimatedProgress = estimatedProgress
self.isProvisionallyNavigating = isProvisionallyNavigating
self.pageURL = pageURL
self.pageTitle = pageTitle
Expand Down Expand Up @@ -124,7 +127,7 @@ public class WebViewNavigator {
webView.loadUrl(urlString ?? "about:blank")
#else
if url.isFileURL {
webView.loadFileURL(url, allowingReadAccessTo: url)
webView.loadFileURL(url, allowingReadAccessTo: url.deletingLastPathComponent())
} else {
webView.load(URLRequest(url: url))
}
Expand Down Expand Up @@ -262,8 +265,7 @@ extension WebView : ViewRepresentable {
}

public func update(webView: PlatformWebView) {
logger.info("WebView.update: \(webView)")
//webView.load(URLRequest(url: url))
//logger.info("WebView.update: \(webView)")
}

#if SKIP
Expand Down Expand Up @@ -319,6 +321,8 @@ extension WebView : ViewRepresentable {

@MainActor private func create(from context: Context) -> WebEngine {
let webEngine = makeWebEngine(id: persistentWebViewID, config: config, coordinator: context.coordinator, messageHandlerNamesToRegister: messageHandlerNamesToRegister)
context.coordinator.navigator.webEngine = webEngine

let webView = webEngine.webView
refreshMessageHandlers(userContentController: webView.configuration.userContentController, context: context)

Expand All @@ -341,9 +345,17 @@ extension WebView : ViewRepresentable {
// add a pull-to-refresh control to the page
webView.scrollView.refreshControl = UIRefreshControl()
webView.scrollView.refreshControl?.addTarget(context.coordinator, action: #selector(Coordinator.handleRefreshControl), for: .valueChanged)

webView.publisher(for: \.estimatedProgress)
.receive(on: DispatchQueue.main)
.sink { progress in
_ = withAnimation(progress == 0.0 ? .none : .easeIn) {
context.coordinator.setLoading(estimatedProgress: progress)
}
}
.store(in: &context.coordinator.subscriptions)
#endif

context.coordinator.navigator.webEngine = webEngine
if context.coordinator.scriptCaller == nil, let scriptCaller = scriptCaller {
context.coordinator.scriptCaller = scriptCaller
}
Expand Down Expand Up @@ -467,6 +479,10 @@ public class WebViewCoordinator: NSObject {

var compiledContentRules = [String: ContentRuleList]()

#if !SKIP
var subscriptions: Set<AnyCancellable> = []
#endif

var messageHandlerNames: [String] {
webView.messageHandlers.keys.map { $0 }
}
Expand All @@ -486,12 +502,17 @@ public class WebViewCoordinator: NSObject {
// }
}

@discardableResult func setLoading(_ isLoading: Bool, pageURL: URL? = nil, isProvisionallyNavigating: Bool? = nil, canGoBack: Bool? = nil, canGoForward: Bool? = nil, backList: [BackForwardListItem]? = nil, forwardList: [BackForwardListItem]? = nil, error: Error? = nil) -> WebViewState {
@discardableResult func setLoading(_ isLoading: Bool? = nil, estimatedProgress: Double? = nil, pageURL: URL? = nil, isProvisionallyNavigating: Bool? = nil, canGoBack: Bool? = nil, canGoForward: Bool? = nil, backList: [BackForwardListItem]? = nil, forwardList: [BackForwardListItem]? = nil, error: Error? = nil) -> WebViewState {
let newState = webView.state
newState.isLoading = isLoading
if let isLoading = isLoading {
newState.isLoading = isLoading
}
if let pageURL = pageURL {
newState.pageURL = pageURL
}
if let estimatedProgress = estimatedProgress {
newState.estimatedProgress = estimatedProgress
}
if let isProvisionallyNavigating = isProvisionallyNavigating {
newState.isProvisionallyNavigating = isProvisionallyNavigating
}
Expand Down Expand Up @@ -568,7 +589,16 @@ extension WebViewCoordinator: ScriptMessageHandler {
@available(macOS 14.0, iOS 17.0, *)
extension WebViewCoordinator: WebUIDelegate {

public func webView(_ webView: WKWebView, contextMenuConfigurationForElement elementInfo: WKContextMenuElementInfo, completionHandler: @escaping (UIContextMenuConfiguration?) -> Void) {
@MainActor public func webView(_ webView: WKWebView, createWebViewWith configuration: WKWebViewConfiguration, for navigationAction: WKNavigationAction, windowFeatures: WKWindowFeatures) -> WKWebView? {
// TODO: open new window
logger.log("TODO createWebViewWith: \(configuration) \(navigationAction)")
if let url = navigationAction.request.url {
navigator.load(url: url, newTab: true)
}
return nil // self.navigator.webEngine?.webView // uncaught exception 'NSInternalInconsistencyException', reason: 'Returned WKWebView was not created with the given configuration.'
}

@MainActor public func webView(_ webView: WKWebView, contextMenuConfigurationForElement elementInfo: WKContextMenuElementInfo, completionHandler: @escaping (UIContextMenuConfiguration?) -> Void) {
guard let url = elementInfo.linkURL else {
completionHandler(nil)
return
Expand All @@ -578,15 +608,12 @@ extension WebViewCoordinator: WebUIDelegate {

let menu = UIMenu(title: "", children: [
UIAction(title: NSLocalizedString("Open", bundle: .module, comment: "context menu action name for opening a url"), image: UIImage(systemName: "plus.square")) { _ in
Task {
await self.navigator.load(url: url, newTab: true)
}
},
UIAction(title: NSLocalizedString("Open in New Tab", bundle: .module, comment: "context menu action name for opening a url in a new tab"), image: UIImage(systemName: "plus.square.on.square")) { _ in
Task {
await self.navigator.load(url: url, newTab: true)
}
self.navigator.load(url: url, newTab: true)
},
// TODO: supportTabs
// UIAction(title: NSLocalizedString("Open in New Tab", bundle: .module, comment: "context menu action name for opening a url in a new tab"), image: UIImage(systemName: "plus.square.on.square")) { _ in
// self.navigator.load(url: url, newTab: true)
// },
UIAction(title: NSLocalizedString("Open in Default Browser", bundle: .module, comment: "context menu action name for opening a url in the system browser"), image: UIImage(systemName: "safari")) { _ in
UIApplication.shared.open(url)
},
Expand Down Expand Up @@ -615,6 +642,7 @@ extension WebViewCoordinator: WebNavigationDelegate {
public func webView(_ webView: PlatformWebView, didFinish navigation: WebNavigation!) {
let newState = setLoading(
false,
estimatedProgress: webView.estimatedProgress,
pageURL: webView.url,
isProvisionallyNavigating: false,
canGoBack: webView.canGoBack,
Expand Down Expand Up @@ -697,6 +725,7 @@ extension WebViewCoordinator: WebNavigationDelegate {
public func webView(_ webView: PlatformWebView, didStartProvisionalNavigation navigation: WebNavigation!) {
setLoading(
true,
estimatedProgress: 0.0,
isProvisionallyNavigating: true,
canGoBack: webView.canGoBack,
canGoForward: webView.canGoForward,
Expand Down

0 comments on commit f61fce3

Please sign in to comment.