Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# Xcode
*.xcodeproj
*.pbxproj
*.xcuserstate
*.xcuserdatad/
*.xcworkspace
Expand Down
10 changes: 5 additions & 5 deletions ora/App/OraRoot.swift
Original file line number Diff line number Diff line change
Expand Up @@ -203,19 +203,19 @@ struct OraRoot: View {
NotificationCenter.default.addObserver(forName: .reloadPage, object: nil, queue: .main) { note in
Task { @MainActor in
guard note.object as? NSWindow === window ?? NSApp.keyWindow else { return }
tabManager.activeTab?.webView.reload()
tabManager.activeTab?.reload()
}
}
NotificationCenter.default.addObserver(forName: .goBack, object: nil, queue: .main) { note in
Task { @MainActor in
guard note.object as? NSWindow === window ?? NSApp.keyWindow else { return }
tabManager.activeTab?.webView.goBack()
tabManager.activeTab?.goBack()
}
}
NotificationCenter.default.addObserver(forName: .goForward, object: nil, queue: .main) { note in
Task { @MainActor in
guard note.object as? NSWindow === window ?? NSApp.keyWindow else { return }
tabManager.activeTab?.webView.goForward()
tabManager.activeTab?.goForward()
}
}
NotificationCenter.default.addObserver(forName: .togglePinTab, object: nil, queue: .main) { note in
Expand Down Expand Up @@ -285,7 +285,7 @@ struct OraRoot: View {
container: activeTab.container
) { [weak toastManager] in
DispatchQueue.main.async {
activeTab.webView.reload()
activeTab.reload()
toastManager?.show("Cache cleared for \(domain)", icon: .system("trash"))
}
}
Expand All @@ -308,7 +308,7 @@ struct OraRoot: View {
container: activeTab.container
) { [weak toastManager] in
DispatchQueue.main.async {
activeTab.webView.reload()
activeTab.reload()
toastManager?.show("Cookies cleared for \(domain)", icon: .system("trash"))
}
}
Expand Down
62 changes: 62 additions & 0 deletions ora/Core/BrowserEngine/BrowserDownloadTask.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import Foundation
@preconcurrency import WebKit

final class BrowserDownloadTask: NSObject, WKDownloadDelegate {
let id = UUID()
var originalURL: URL
var onDestinationRequest: ((URLResponse, String, @escaping (URL?) -> Void) -> Void)?
var onRedirect: ((URL) -> Void)?
var onFinish: (() -> Void)?
var onFail: ((Error) -> Void)?

private let download: WKDownload

init(download: WKDownload, originalURL: URL) {
self.download = download
self.originalURL = originalURL
super.init()
self.download.delegate = self
}

var progress: Progress {
download.progress
}

func cancel() {
download.cancel()
}

func download(
_ download: WKDownload,
decideDestinationUsing response: URLResponse,
suggestedFilename: String,
completionHandler: @escaping (URL?) -> Void
) {
if let onDestinationRequest {
onDestinationRequest(response, suggestedFilename, completionHandler)
} else {
completionHandler(nil)
}
}

func download(
_ download: WKDownload,
willPerformHTTPRedirection response: HTTPURLResponse,
newRequest: URLRequest,
decisionHandler: @escaping (WKDownload.RedirectPolicy) -> Void
) {
if let url = newRequest.url {
originalURL = url
onRedirect?(url)
}
decisionHandler(.allow)
}

func downloadDidFinish(_ download: WKDownload) {
onFinish?()
}

func download(_ download: WKDownload, didFailWithError error: Error) {
onFail?(error)
}
}
66 changes: 66 additions & 0 deletions ora/Core/BrowserEngine/BrowserEngine.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import Foundation

struct BrowserPageConfiguration {
let userAgent: String?
let allowsPictureInPicture: Bool
let allowsJavaScript: Bool
let allowsJavaScriptWindowsAutomatically: Bool
let allowsAirPlayForMediaPlayback: Bool
let allowsInspectableDebugging: Bool
let allowsBackForwardNavigationGestures: Bool
let mediaPlaybackRequiresUserAction: Bool
let scriptMessageNames: [String]
let userScripts: [BrowserUserScript]

static func oraDefault(userScripts: [BrowserUserScript]) -> BrowserPageConfiguration {
BrowserPageConfiguration(
userAgent: "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/26.0.1 Safari/605.1.15",
allowsPictureInPicture: true,
allowsJavaScript: true,
allowsJavaScriptWindowsAutomatically: false,
allowsAirPlayForMediaPlayback: true,
allowsInspectableDebugging: true,
allowsBackForwardNavigationGestures: true,
mediaPlaybackRequiresUserAction: false,
scriptMessageNames: ["listener", "linkHover", "mediaEvent", "passwordManager"],
userScripts: userScripts
)
}
}

final class BrowserEngine {
private struct ProfileKey: Hashable {
let identifier: UUID
let isPrivate: Bool
}

static let shared = BrowserEngine()
private let profileCacheLock = NSLock()
private var profileCache: [ProfileKey: BrowserEngineProfile] = [:]

func makeProfile(identifier: UUID, isPrivate: Bool) -> BrowserEngineProfile {
if isPrivate {
return BrowserEngineProfile(identifier: identifier, isPrivate: true)
}

let key = ProfileKey(identifier: identifier, isPrivate: false)
profileCacheLock.lock()
defer { profileCacheLock.unlock() }

if let profile = profileCache[key] {
return profile
}

let profile = BrowserEngineProfile(identifier: identifier, isPrivate: false)
profileCache[key] = profile
return profile
}
Comment thread
yonaries marked this conversation as resolved.

func makePage(
profile: BrowserEngineProfile,
configuration: BrowserPageConfiguration,
delegate: BrowserPageDelegate?
) -> BrowserPage {
BrowserPage(profile: profile, configuration: configuration, delegate: delegate)
}
}
63 changes: 63 additions & 0 deletions ora/Core/BrowserEngine/BrowserEngineProfile.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import Foundation
@preconcurrency import WebKit

final class BrowserEngineProfile {
let identifier: UUID
let isPrivate: Bool
let dataStore: WKWebsiteDataStore

init(identifier: UUID, isPrivate: Bool) {
self.identifier = identifier
self.isPrivate = isPrivate
if isPrivate {
dataStore = WKWebsiteDataStore.nonPersistent()
} else {
dataStore = WKWebsiteDataStore(forIdentifier: identifier)
}
}

func clearData(
ofTypes types: Set<BrowserWebsiteDataType>,
forHost host: String? = nil,
completion: (() -> Void)? = nil
) {
let mappedTypes = mapWebsiteDataTypes(types)
guard let host, !host.isEmpty else {
dataStore.removeData(ofTypes: mappedTypes, modifiedSince: .distantPast) {
completion?()
}
return
}

dataStore.fetchDataRecords(ofTypes: mappedTypes) { records in
let targetRecords = records.filter { $0.displayName.contains(host) }
guard !targetRecords.isEmpty else {
completion?()
return
}

self.dataStore.removeData(ofTypes: mappedTypes, for: targetRecords) {
completion?()
}
}
}

private func mapWebsiteDataTypes(_ types: Set<BrowserWebsiteDataType>) -> Set<String> {
if types.contains(.all) {
return WKWebsiteDataStore.allWebsiteDataTypes()
}

var mapped: Set<String> = []
if types.contains(.cookies) {
mapped.insert(WKWebsiteDataTypeCookies)
}
if types.contains(.cache) {
mapped.formUnion([
WKWebsiteDataTypeDiskCache,
WKWebsiteDataTypeMemoryCache,
WKWebsiteDataTypeFetchCache
])
}
Comment thread
yonaries marked this conversation as resolved.
return mapped
}
}
71 changes: 71 additions & 0 deletions ora/Core/BrowserEngine/BrowserEngineTypes.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import AppKit
import Foundation

enum BrowserWebsiteDataType: Hashable {
case cookies
case cache
case all
}

enum BrowserUserScriptInjectionTime {
case atDocumentStart
case atDocumentEnd
}

struct BrowserUserScript {
let name: String?
let source: String
let injectionTime: BrowserUserScriptInjectionTime
let forMainFrameOnly: Bool
}

struct BrowserScriptMessage {
let name: String
let body: Any?
}

struct BrowserOpenPanelOptions {
let allowsDirectories: Bool
let allowsMultipleSelection: Bool
}

enum BrowserPermissionKind {
case mediaCapture
}

enum BrowserPermissionDecision {
case grant
case deny
}

struct BrowserNavigationAction {
let request: URLRequest
let modifierFlags: NSEvent.ModifierFlags
}

enum BrowserNavigationActionDisposition {
case allow
case cancel
case openInNewTab
}

enum BrowserNavigationPhase {
case started
case committed
case finished
}

struct BrowserNavigationEvent {
let phase: BrowserNavigationPhase
let url: URL?
let title: String?
let progress: Double
let isLoading: Bool
}

struct BrowserSnapshotConfiguration {
let rect: CGRect?
let afterScreenUpdates: Bool

static let full = BrowserSnapshotConfiguration(rect: nil, afterScreenUpdates: false)
}
Loading
Loading