Author SHA1 Message Date
buzzert 90233b6a21 Simplify TestFlight automation
TestFlight / Build and upload (push) Successful in 1m33s
2026-07-27 17:30:02 -07:00
buzzertandClaude Sonnet 5 45b09a13d3 ci: put CI keychain first in codesign search list
TestFlight / testflight (push) Successful in 1m27s
codesign resolves signing identities through the user keychain search
list (first match wins) and ignores --keychain for the lookup. This
runner hosts another project (Sybil-2) whose keychain holds the same
Apple Distribution identity, so if that keychain is locked and appears
earlier in the search list, codesign fails with errSecInternalComponent
no matter how correctly our own keychain is set up. Prepend the fresh
CI keychain to the search list for the build and always delete it
afterward, which restores the original list.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-11 13:16:41 -07:00
buzzert 1632e5fbd4 ITSexempt
TestFlight / testflight (push) Successful in 1m12s
2026-07-06 09:24:36 -07:00
buzzert 18b63cbd19 Attempt to fix stale tabs losing info 2026-07-06 09:24:36 -07:00
buzzert 4371f051aa Switch to using content blockers instead of scheme handlers 2026-07-06 09:24:36 -07:00
buzzert 2c09157448 gitea: try removing temporary keychain
TestFlight / testflight (push) Failing after 1m39s
2026-07-06 09:07:22 -07:00
buzzert 8e26f0a066 Use match signing team for deployment
TestFlight / testflight (push) Failing after 19s
2026-07-06 08:43:11 -07:00
buzzert db947b1827 Set up TestFlight deployment
TestFlight / testflight (push) Failing after 24s
2026-07-06 08:36:11 -07:00
buzzert e53cde7f60 Merge pull request 'codex: add custom search engines' (#1) from feature/custom-search-engines into master
Reviewed-on: #1

Good on iOS, still need to do it on macOS (#2)
2025-09-29 23:09:41 +00:00
buzzert 62e36359b8 finish implementation for iOS 2025-09-29 16:09:00 -07:00
buzzert 265d393cdc codex: add custom search engines 2025-09-28 21:10:31 -07:00
buzzert 797c4c7c52 darkmode css: remove em from reinvert list 2025-09-18 08:52:15 -07:00
buzzert 6cc4694e1e attempt to fix stale tabs 2025-09-06 22:56:51 -07:00
buzzert 334703ec43 Obscure mail spi 2025-05-05 19:05:49 -07:00
buzzert 6301c40c59 Obscure WebKit SPI 2025-05-05 19:01:46 -07:00
buzzert 8fe8426cc2 Delete web process bundle in favor of custom scheme handler 2025-05-05 18:14:46 -07:00
buzzert a2e29abf19 Fixup for distribution 2025-04-11 19:53:02 -07:00
buzzert 2f4b3c64fe Move off of internal SDK, copy webkit spis from opensource 2025-04-11 18:27:59 -07:00
James Magahern 177db26b69 Implements zoom in/out keyboard shortcuts 2024-10-17 13:38:41 -07:00
buzzert 0fd466d86d Adds keyboard shortcuts for changing script policy 2024-08-05 18:09:04 -07:00
buzzert 69aa02e303 Resolves "New Tab" bug after web content process jettison 2024-08-05 17:36:59 -07:00
James Magahern 29057c8024 History menu improvements 2024-07-31 17:53:23 -07:00
buzzert a0056af8b1 Update for iOS 17. 2024-07-29 19:21:51 -07:00
James Magahern 6dccb25b3e TitlebarView: move to drag api 2024-05-10 17:01:14 -07:00
James Magahern eec57f5f31 History UI improvements 2023-09-26 16:23:35 -07:00
buzzert d9fa5adf00 Adds README and screenshot 2023-09-17 20:30:21 -07:00
James Magahern a4c6caeb8e Another attempt to fix empty URL bar 2023-08-10 19:52:59 -07:00
James Magahern 3f5d608908 Even better AI generated icon 2023-04-19 14:39:06 -07:00
James Magahern 99bd3af187 Fix warnings 2023-04-19 14:33:17 -07:00
James Magahern 98dfd85781 Small Settings QOL improvements 2023-04-19 14:30:06 -07:00
James Magahern 6a187b0df3 Update icon to use Midjourney generated one 2023-04-19 11:45:21 -07:00
James Magahern dca5aca1ce UIGraphicsImageContext: both dimensions must be > 0 2023-04-19 11:40:57 -07:00
James Magahern f8d19414d5 Avoid generating zero size bitmaps 2023-04-14 19:37:31 -07:00
James Magahern 748023d330 Adds history menu 2023-01-27 13:46:23 -08:00
buzzert 6f41caff62 HistoryView: Compact UI for phones 2023-01-25 16:13:09 -08:00
buzzert 34ca35ea5a HistoryView: Adds ability to delete history items 2023-01-25 15:04:58 -08:00
James Magahern 53efb5389e Adds history browser view 2023-01-20 17:28:15 -08:00
James Magahern f374f3ebe8 Tweak autocomplete query a bit 2023-01-20 16:20:36 -08:00
James Magahern 836daee5a3 Fix find and replace 2023-01-11 13:20:36 -08:00
buzzert 12e2d82009 Attempt to fix blank URL bar bug 2022-10-11 15:04:59 -07:00
buzzert 6be801420c iOS 16 API updates 2022-10-11 15:04:37 -07:00
James Magahern 75153d1c4a Settings: Catalyst: adjust slightly the header labels 2022-08-22 17:43:04 -07:00
125 changed files with 4479 additions and 822 deletions
+5
View File
@@ -0,0 +1,5 @@
ASC_KEY_ID=
ASC_ISSUER_ID=
ASC_KEY=
MATCH_PASSWORD=
MATCH_GIT_BASIC_AUTHORIZATION=
+35
View File
@@ -0,0 +1,35 @@
name: TestFlight
on:
push:
tags:
- "release/ios/v*"
- "Attractor-*"
jobs:
testflight:
name: Build and upload
runs-on: macos-arm64
timeout-minutes: 90
steps:
- name: Check out the release tag
uses: actions/checkout@v4
- name: Set up Ruby
uses: ruby/setup-ruby@v1
with:
ruby-version: "3.3.11"
bundler-cache: true
- name: Build and upload to TestFlight
env:
ASC_KEY_ID: ${{ secrets.ASC_KEY_ID }}
ASC_ISSUER_ID: ${{ secrets.ASC_ISSUER_ID }}
ASC_KEY: ${{ secrets.ASC_KEY }}
MATCH_PASSWORD: ${{ secrets.MATCH_PASSWORD }}
MATCH_GIT_BASIC_AUTHORIZATION: ${{ secrets.MATCH_GIT_BASIC_AUTHORIZATION }}
CI: "true"
FASTLANE_SKIP_UPDATE_CHECK: "1"
FASTLANE_HIDE_CHANGELOG: "1"
run: bundle exec fastlane ios beta
+5
View File
@@ -0,0 +1,5 @@
.env
.bundle/
build/
fastlane/README.md
fastlane/report.xml
+25
View File
@@ -32,6 +32,28 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
return UISceneConfiguration(name: "Browser", sessionRole: connectingSceneSession.role) return UISceneConfiguration(name: "Browser", sessionRole: connectingSceneSession.role)
} }
private func historyMenu() -> UIMenu {
let historyItems = BrowserHistory.shared.allHistory(limit: 50).map { item in
let title = ((item.title.count > 0) ? item.title : item.url.absoluteString)
.middleTruncatedString(maximumLength: 85)
return UIAction(title: title) { action in
UIApplication.shared.sendAction(#selector(ShortcutResponder.handleOpenURL), to: nil, from: action, for: OpenURLEvent(url: item.url))
}
}
return UIMenu(title: "History", children: [
UIKeyCommand(
modifiers: [ .command ],
input: "y",
title: "Show All History…",
action: #selector(ShortcutResponder.showHistory)
),
UIMenu(options: .displayInline, children: historyItems)
])
}
override var keyCommands: [UIKeyCommand]? { override var keyCommands: [UIKeyCommand]? {
get { KeyboardShortcuts.allKeyCommands() } get { KeyboardShortcuts.allKeyCommands() }
} }
@@ -68,6 +90,9 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
// Go // Go
builder.insertSibling(UIMenu(title: "Go", children: KeyboardShortcuts.menu(for: .go)), beforeMenu: .view) builder.insertSibling(UIMenu(title: "Go", children: KeyboardShortcuts.menu(for: .go)), beforeMenu: .view)
// History
builder.insertSibling(historyMenu(), beforeMenu: .view)
// View // View
builder.replaceChildren(ofMenu: .view) { children in builder.replaceChildren(ofMenu: .view) { children in
KeyboardShortcuts.menu(for: .view) + children KeyboardShortcuts.menu(for: .view) + children
+117 -4
View File
@@ -7,9 +7,58 @@
import Foundation import Foundation
import CoreData import CoreData
import Combine
class BrowserHistory class BrowserHistory
{ {
class ViewModel: NSObject, ObservableObject, NSFetchedResultsControllerDelegate {
@Published var searchQuery: String = ""
@Published private(set) var historyItems: [HistoryItem] = []
fileprivate let historyController: BrowserHistory
fileprivate var searchQueryObserver: AnyCancellable? = nil
fileprivate var fetchedResultsController: NSFetchedResultsController<HistoryItemEntity> {
didSet { fetchedResultsController.delegate = self; performInitialFetch() }
}
fileprivate init(fetchedResultsController: NSFetchedResultsController<HistoryItemEntity>, historyController: BrowserHistory) {
self.fetchedResultsController = fetchedResultsController
self.historyController = historyController
super.init()
searchQueryObserver = $searchQuery.sink { [unowned self] newValue in
self.fetchedResultsController = historyController.fetchRequestController(forQuery: newValue)
}
}
public func item(forIdentifier identifier: HistoryItem.ID) -> HistoryItem? {
if let object = fetchedResultsController.managedObjectContext.object(with: identifier) as? HistoryItemEntity {
return HistoryItem(entity: object)
}
return nil
}
public func deleteItems(_ items: Set<HistoryItem.ID>) {
items.forEach { identifier in
historyController.deleteItem(withIdentifier: identifier)
}
}
func controllerDidChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
if let objects = controller.fetchedObjects as? [HistoryItemEntity] {
self.historyItems = objects.map { HistoryItem(entity: $0) }
}
}
fileprivate func performInitialFetch() {
try? fetchedResultsController.performFetch()
if let objects = fetchedResultsController.fetchedObjects {
self.historyItems = objects.map { HistoryItem(entity: $0) }
}
}
}
static public let shared = BrowserHistory() static public let shared = BrowserHistory()
lazy fileprivate var persistentContainer: NSPersistentContainer = { lazy fileprivate var persistentContainer: NSPersistentContainer = {
@@ -35,12 +84,43 @@ class BrowserHistory
let nserror = error as NSError let nserror = error as NSError
fatalError("Failed saving persistent entity to store: \(nserror), \(nserror.userInfo)") fatalError("Failed saving persistent entity to store: \(nserror), \(nserror.userInfo)")
} }
UIMenuSystem.main.setNeedsRebuild()
} }
public func allHistory() -> [HistoryItem] { public func viewModel(forFilterString filterString: String? = nil, limit: Int = 500) -> ViewModel {
let dataContext = persistentContainer.viewContext let resultsController = fetchRequestController(forQuery: filterString, limit: limit)
return ViewModel(fetchedResultsController: resultsController, historyController: self)
}
let fetchRequest: NSFetchRequest<HistoryItemEntity> = HistoryItemEntity.fetchRequest() public func historyItem(forIdentifier: HistoryItem.ID) -> HistoryItem? {
if let entity = try? persistentContainer.viewContext.existingObject(with: forIdentifier) as? HistoryItemEntity {
return HistoryItem(entity: entity)
}
return nil
}
public func deleteItem(withIdentifier identifier: HistoryItem.ID) {
let dataContext = persistentContainer.viewContext
if let object = try? dataContext.existingObject(with: identifier) {
dataContext.delete(object)
try? dataContext.save()
}
}
public func clearAllHistory() {
let dataContext = persistentContainer.viewContext
let fetchRequest: NSFetchRequest<NSFetchRequestResult> = HistoryItemEntity.fetchRequest()
let request = NSBatchDeleteRequest(fetchRequest: fetchRequest)
do { try dataContext.execute(request) }
catch { print("Error clearing history: \(error.localizedDescription)") }
}
public func allHistory(filteredBy filterString: String? = nil, limit: Int = 500) -> [HistoryItem] {
let dataContext = persistentContainer.viewContext
let fetchRequest = fetchRequest(forStringContaining: filterString, limit: limit)
let entities: [HistoryItemEntity] = (try? dataContext.fetch(fetchRequest)) ?? [] let entities: [HistoryItemEntity] = (try? dataContext.fetch(fetchRequest)) ?? []
return entities.map { (entity) -> HistoryItem in return entities.map { (entity) -> HistoryItem in
@@ -52,7 +132,11 @@ class BrowserHistory
let dataContext = persistentContainer.viewContext let dataContext = persistentContainer.viewContext
let fetchRequest: NSFetchRequest<HistoryItemEntity> = HistoryItemEntity.fetchRequest() let fetchRequest: NSFetchRequest<HistoryItemEntity> = HistoryItemEntity.fetchRequest()
fetchRequest.predicate = NSPredicate(format: "host CONTAINS %@ OR title contains %@", matching, matching) fetchRequest.predicate = NSPredicate(format: """
host CONTAINS[cd] %@
OR title CONTAINS[cd] %@
OR url ENDSWITH[cd] %@
""", matching, matching, matching)
fetchRequest.fetchLimit = 200 fetchRequest.fetchLimit = 200
fetchRequest.sortDescriptors = [ NSSortDescriptor(key: "visitCount", ascending: false) ] fetchRequest.sortDescriptors = [ NSSortDescriptor(key: "visitCount", ascending: false) ]
@@ -85,6 +169,35 @@ class BrowserHistory
} }
} }
extension BrowserHistory
{
fileprivate func fetchRequestController(forQuery query: String? = nil, limit: Int = 500) -> NSFetchedResultsController<HistoryItemEntity> {
let fetchRequest = fetchRequest(forStringContaining: query, limit: limit)
return NSFetchedResultsController(fetchRequest: fetchRequest,
managedObjectContext: persistentContainer.viewContext,
sectionNameKeyPath: nil, cacheName: nil)
}
fileprivate func fetchRequest(forStringContaining filterString: String? = nil, limit: Int = 500) -> NSFetchRequest<HistoryItemEntity> {
let fetchRequest: NSFetchRequest<HistoryItemEntity> = HistoryItemEntity.fetchRequest()
fetchRequest.fetchLimit = limit
fetchRequest.sortDescriptors = [
// Sort by date
NSSortDescriptor(keyPath: \HistoryItemEntity.lastVisited, ascending: false)
]
if let filterString, filterString.count > 0 {
fetchRequest.predicate = NSPredicate(format: """
host CONTAINS[cd] %@
OR title CONTAINS[cd] %@
OR url CONTAINS[cd] %@
""", filterString, filterString, filterString)
}
return fetchRequest
}
}
extension URL extension URL
{ {
public func topLevelURL() -> URL { public func topLevelURL() -> URL {
+12 -1
View File
@@ -5,17 +5,28 @@
// Created by James Magahern on 8/14/20. // Created by James Magahern on 8/14/20.
// //
import CoreData
import Foundation import Foundation
struct HistoryItem: Hashable struct HistoryItem: Hashable, Identifiable
{ {
var url: URL var url: URL
var title: String var title: String
var lastVisited: Date var lastVisited: Date
var id: NSManagedObjectID
init(entity: HistoryItemEntity) { init(entity: HistoryItemEntity) {
self.url = entity.url ?? URL(string: "about:blank")! self.url = entity.url ?? URL(string: "about:blank")!
self.lastVisited = entity.lastVisited ?? Date() self.lastVisited = entity.lastVisited ?? Date()
self.title = entity.title ?? "" self.title = entity.title ?? ""
self.id = entity.objectID
}
// For testing/previews
public init(url: URL, title: String, lastVisited: Date) {
self.url = url
self.title = title
self.lastVisited = lastVisited
self.id = NSManagedObjectID()
} }
} }
+2 -1
View File
@@ -31,7 +31,8 @@ class ResourcePolicyManager: NSObject, SBRResourceOriginPolicyDataSource
func allowedOriginsForScriptResources() -> Set<String> { allowedOriginSet } func allowedOriginsForScriptResources() -> Set<String> { allowedOriginSet }
func scriptPolicyTypeByOrigin() -> [String : NSNumber] { func scriptPolicyTypeByOrigin() -> [String : NSNumber] {
return scriptPolicies.mapValues { NSNumber(integerLiteral: $0.rawValue) } let types = scriptPolicies.mapValues { NSNumber(integerLiteral: $0.rawValue) }
return types
} }
private lazy var scriptPolicies: Dictionary<String, ScriptPolicy.PolicyType> = { private lazy var scriptPolicies: Dictionary<String, ScriptPolicy.PolicyType> = {
+1
View File
@@ -98,6 +98,7 @@ class BrowserView: UIView
} }
}(animationCurve) }(animationCurve)
toolbarView?.layoutLatch.deactivate()
keyboardLayoutOffset = bounds.height - keyboardEndFrame.minY keyboardLayoutOffset = bounds.height - keyboardEndFrame.minY
UIView.animate(withDuration: animationDuration, delay: 0.0, options: animationOptions, animations: { self.layoutIfNeeded() }, completion: nil) UIView.animate(withDuration: animationDuration, delay: 0.0, options: animationOptions, animations: { self.layoutIfNeeded() }, completion: nil)
} }
@@ -94,11 +94,11 @@ extension BrowserViewController: ShortcutResponder
} }
func goBack(_ sender: Any?) { func goBack(_ sender: Any?) {
tab.webView.goBack() currentTab.webView.goBack()
} }
func goForward(_ sender: Any?) { func goForward(_ sender: Any?) {
tab.webView.goForward() currentTab.webView.goForward()
} }
func createTab(_ sender: Any?) { func createTab(_ sender: Any?) {
@@ -106,24 +106,24 @@ extension BrowserViewController: ShortcutResponder
} }
func previousTab(_ sender: Any?) { func previousTab(_ sender: Any?) {
if let tabIndex = tabController.tabs.firstIndex(of: self.tab) { if let tabIndex = tabController.tabs.firstIndex(of: self.currentTab) {
if tabIndex - 1 >= 0 { if tabIndex - 1 >= 0 {
self.tab = tabController.tabs[tabIndex - 1] self.currentTab = tabController.tabs[tabIndex - 1]
} }
} }
} }
func nextTab(_ sender: Any?) { func nextTab(_ sender: Any?) {
if let tabIndex = tabController.tabs.firstIndex(of: self.tab) { if let tabIndex = tabController.tabs.firstIndex(of: self.currentTab) {
if tabIndex + 1 < tabController.tabs.count { if tabIndex + 1 < tabController.tabs.count {
self.tab = tabController.tabs[tabIndex + 1] self.currentTab = tabController.tabs[tabIndex + 1]
} }
} }
} }
func closeTab(_ sender: Any?) { func closeTab(_ sender: Any?) {
if tabController.tabs.count > 1 { if tabController.tabs.count > 1 {
tabController.closeTab(self.tab) tabController.closeTab(self.currentTab)
} else { } else {
#if targetEnvironment(macCatalyst) #if targetEnvironment(macCatalyst)
if let originWindowScene = self.view.window?.windowScene { if let originWindowScene = self.view.window?.windowScene {
@@ -144,18 +144,30 @@ extension BrowserViewController: ShortcutResponder
webView.reload() webView.reload()
} }
func stop(_ sender: Any?) {
webView.stopLoading()
}
override func increaseSize(_ sender: Any?) { override func increaseSize(_ sender: Any?) {
tab.webView._viewScale += 0.10 currentTab.webView._viewScale += 0.10
} }
override func decreaseSize(_ sender: Any?) { override func decreaseSize(_ sender: Any?) {
tab.webView._viewScale -= 0.10 currentTab.webView._viewScale -= 0.10
}
func zoomToActualSize(_ sender: Any?) {
currentTab.webView._viewScale = 1.0
} }
func showPreferences(_ sender: Any?) { func showPreferences(_ sender: Any?) {
showSettingsWindow() showSettingsWindow()
} }
func showHistory(_ sender: Any?) {
showHistoryWindow()
}
func toggleDarkMode(_ sender: Any?) { func toggleDarkMode(_ sender: Any?) {
self.darkModeEnabled = !self.darkModeEnabled self.darkModeEnabled = !self.darkModeEnabled
} }
@@ -163,4 +175,50 @@ extension BrowserViewController: ShortcutResponder
func openInReaderMode(_ sender: Any?) { func openInReaderMode(_ sender: Any?) {
showReaderWindow() showReaderWindow()
} }
func handleOpenURL(_ sender: Any?, forEvent event: OpenURLEvent?) {
guard let event else { return }
if currentTab.url == nil {
currentTab.beginLoadingURL(event.url)
} else {
createNewTab(withURL: event.url)
}
}
func raiseScriptPolicyRestriction(_ sender: Any?) {
guard let hostOrigin = currentTab.webView.url?.securityOrigin else { return }
let currentPolicy = policyManager.scriptPolicy(forOrigin: hostOrigin)
policyManager.setScriptPolicyType(currentPolicy.policyType--, forOrigin: hostOrigin)
didChangeScriptPolicy()
}
func lowerScriptPolicyRestriction(_ sender: Any?) {
guard let hostOrigin = currentTab.webView.url?.securityOrigin else { return }
let currentPolicy = policyManager.scriptPolicy(forOrigin: hostOrigin)
policyManager.setScriptPolicyType(currentPolicy.policyType++, forOrigin: hostOrigin)
didChangeScriptPolicy()
}
}
extension ScriptPolicy.PolicyType {
static postfix func ++ (obj: ScriptPolicy.PolicyType) -> ScriptPolicy.PolicyType {
switch obj {
case .alpha: .bravo
case .bravo: .charlie
case .charlie: .delta
case .delta: .echo
default: obj
}
}
static postfix func -- (obj: ScriptPolicy.PolicyType) -> ScriptPolicy.PolicyType {
switch obj {
case .bravo: .alpha
case .charlie: .bravo
case .delta: .charlie
case .echo: .delta
default: obj
}
}
} }
@@ -10,35 +10,15 @@ import WebKit
extension BrowserViewController: WKNavigationDelegate, WKUIDelegate extension BrowserViewController: WKNavigationDelegate, WKUIDelegate
{ {
func webView(_ webView: WKWebView, didStartProvisionalNavigation navigation: WKNavigation!) { func webView(_ webView: WKWebView, didStartProvisionalNavigation navigation: WKNavigation!) {
tab.loadError = nil if let tab = tabController.tab(forWebView: webView) {
// We're alive!
// Check to make sure we have connected to the web content process tab.contentProcessTerminated = false
if !tab.bridge.webContentProcessConnected { tab.loadError = nil
// This means we started loading a page but the web content process hasn't loaded, which means
// scripts are not getting blocked.
// If you're ad-hoc signing this, you'll need to disable library validation:
// sudo defaults write /Library/Preferences/com.apple.security.libraryvalidation DisableLibraryValidation -bool YES
DispatchQueue.main.async { [unowned self] in
// Stop loading now
webView.stopLoading()
// Show an alert
let alert = UIAlertController(title: "Web Process Not Loaded",
message: "The web content process never contacted the host application",
preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "OK", style: .default, handler: { _ in
alert.dismiss(animated: true, completion: nil)
}))
present(alert, animated: true, completion: nil)
}
} }
// Reset tracking this // Reset tracking this
tab.allowedScriptOrigins.removeAll() currentTab.allowedScriptOrigins.removeAll()
tab.blockedScriptOrigins.removeAll() currentTab.blockedScriptOrigins.removeAll()
updateScriptBlockerButton() updateScriptBlockerButton()
// Blur url bar if applicable // Blur url bar if applicable
@@ -48,7 +28,7 @@ extension BrowserViewController: WKNavigationDelegate, WKUIDelegate
if let url = webView.url { if let url = webView.url {
// Start requesting favicon // Start requesting favicon
tab.updateFaviconForURL(url) currentTab.updateFaviconForURL(url)
} }
} }
@@ -56,7 +36,7 @@ extension BrowserViewController: WKNavigationDelegate, WKUIDelegate
// We got rugged!!!!!! Update the favicon again. // We got rugged!!!!!! Update the favicon again.
if let url = webView.url { if let url = webView.url {
// Start requesting favicon // Start requesting favicon
tab.updateFaviconForURL(url) currentTab.updateFaviconForURL(url)
} }
} }
@@ -90,7 +70,7 @@ extension BrowserViewController: WKNavigationDelegate, WKUIDelegate
return return
} }
var allowJavaScript = tab.javaScriptEnabled var allowJavaScript = currentTab.javaScriptEnabled
if !allowJavaScript, let host = navigationAction.request.url?.host { if !allowJavaScript, let host = navigationAction.request.url?.host {
// Check origin policy // Check origin policy
allowJavaScript = policyManager.scriptPolicy(forOrigin: host).allowsEmbeddedJavaScript() allowJavaScript = policyManager.scriptPolicy(forOrigin: host).allowsEmbeddedJavaScript()
@@ -101,7 +81,7 @@ extension BrowserViewController: WKNavigationDelegate, WKUIDelegate
if let url = navigationAction.request.url, if let url = navigationAction.request.url,
let redirectedURL = Settings.shared.redirectRule(for: url) let redirectedURL = Settings.shared.redirectRule(for: url)
{ {
tab.beginLoadingURL(redirectedURL) currentTab.beginLoadingURL(redirectedURL)
decisionHandler(.cancel, preferences) decisionHandler(.cancel, preferences)
} else { } else {
decisionHandler(.allow, preferences) decisionHandler(.allow, preferences)
@@ -109,17 +89,21 @@ extension BrowserViewController: WKNavigationDelegate, WKUIDelegate
} }
func webView(_ webView: WKWebView, didFail navigation: WKNavigation!, withError error: Error) { func webView(_ webView: WKWebView, didFail navigation: WKNavigation!, withError error: Error) {
if webView == tab.webView { if webView == currentTab.webView {
tab.loadError = error currentTab.loadError = error
} }
} }
func webView(_ webView: WKWebView, didFailProvisionalNavigation navigation: WKNavigation!, withError error: Error) { func webView(_ webView: WKWebView, didFailProvisionalNavigation navigation: WKNavigation!, withError error: Error) {
if webView == tab.webView { if webView == currentTab.webView {
tab.loadError = error currentTab.loadError = error
} }
} }
func webViewWebContentProcessDidTerminate(_ webView: WKWebView) {
tabController.tab(forWebView: webView)?.contentProcessTerminated = true
}
// MARK: WKUIDelegate // MARK: WKUIDelegate
func webView(_ webView: WKWebView, createWebViewWith configuration: WKWebViewConfiguration, for navigationAction: WKNavigationAction, windowFeatures: WKWindowFeatures) -> WKWebView? func webView(_ webView: WKWebView, createWebViewWith configuration: WKWebViewConfiguration, for navigationAction: WKNavigationAction, windowFeatures: WKWindowFeatures) -> WKWebView?
@@ -127,7 +111,7 @@ extension BrowserViewController: WKNavigationDelegate, WKUIDelegate
let newTab = tabController.createNewTab(url: nil, webViewConfiguration: configuration) let newTab = tabController.createNewTab(url: nil, webViewConfiguration: configuration)
newTab.webView.load(navigationAction.request) newTab.webView.load(navigationAction.request)
self.tab = newTab self.currentTab = newTab
return newTab.webView return newTab.webView
} }
+123 -62
View File
@@ -13,8 +13,8 @@ import UniformTypeIdentifiers
class BrowserViewController: UIViewController class BrowserViewController: UIViewController
{ {
let browserView = BrowserView() let browserView = BrowserView()
var tab: Tab { didSet { didChangeTab(tab) } } var currentTab: Tab { didSet { didChangeTab(currentTab) } }
var webView: WKWebView { tab.webView } var webView: WKWebView { currentTab.webView }
internal let tabController = TabController() internal let tabController = TabController()
internal let tabBarViewController: TabBarViewController internal let tabBarViewController: TabBarViewController
@@ -42,9 +42,9 @@ class BrowserViewController: UIViewController
static let longPressWindowButtonToMakeNewTab: Bool = false static let longPressWindowButtonToMakeNewTab: Bool = false
internal var darkModeEnabled: Bool { internal var darkModeEnabled: Bool {
get { tab.bridge.darkModeEnabled } get { currentTab.bridge.darkModeEnabled }
set { set {
tab.bridge.darkModeEnabled = newValue currentTab.bridge.darkModeEnabled = newValue
toolbarController.darkModeEnabled = newValue toolbarController.darkModeEnabled = newValue
} }
} }
@@ -60,7 +60,7 @@ class BrowserViewController: UIViewController
internal var changingFocusToAutocompleteController = false internal var changingFocusToAutocompleteController = false
init() { init() {
self.tab = tabController.tabs.first! self.currentTab = tabController.tabs.first!
self.tabBarViewController = TabBarViewController(tabController: tabController) self.tabBarViewController = TabBarViewController(tabController: tabController)
super.init(nibName: nil, bundle: nil) super.init(nibName: nil, bundle: nil)
@@ -70,7 +70,7 @@ class BrowserViewController: UIViewController
addChild(findOnPageController) addChild(findOnPageController)
addChild(tabBarViewController) addChild(tabBarViewController)
didChangeTab(tab) didChangeTab(currentTab)
} }
required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") }
@@ -114,12 +114,12 @@ class BrowserViewController: UIViewController
// Script button // Script button
toolbarController.scriptControllerIconView.addAction(UIAction(handler: { [unowned self] action in toolbarController.scriptControllerIconView.addAction(UIAction(handler: { [unowned self] action in
let hostOrigin = self.webView.url?.host ?? "" let hostOrigin = self.webView.url?.securityOrigin ?? ""
let loadedScripts = self.tab.allowedScriptOrigins.union(self.tab.blockedScriptOrigins) let loadedScripts = currentTab.allowedScriptOrigins.union(currentTab.blockedScriptOrigins)
let scriptViewController = ScriptPolicyViewController(policyManager: self.policyManager, let scriptViewController = ScriptPolicyViewController(policyManager: self.policyManager,
hostOrigin: hostOrigin, hostOrigin: hostOrigin,
loadedScripts: loadedScripts, loadedScripts: loadedScripts,
scriptsAllowedForTab: self.tab.javaScriptEnabled) scriptsAllowedForTab: currentTab.javaScriptEnabled)
scriptViewController.delegate = self scriptViewController.delegate = self
let navController = UINavigationController(rootViewController: scriptViewController) let navController = UINavigationController(rootViewController: scriptViewController)
@@ -141,7 +141,7 @@ class BrowserViewController: UIViewController
tabPickerController.delegate = self tabPickerController.delegate = self
tabPickerController.title = "Tabs" tabPickerController.title = "Tabs"
tabPickerController.tabBarItem.image = UIImage(systemName: "rectangle.on.rectangle") tabPickerController.tabBarItem.image = UIImage(systemName: "rectangle.on.rectangle")
tabPickerController.selectedTabIdentifier = self.tab.identifier tabPickerController.selectedTabIdentifier = currentTab.identifier
tabPickerController.tabObserver = tabController.$tabs tabPickerController.tabObserver = tabController.$tabs
.receive(on: RunLoop.main) .receive(on: RunLoop.main)
.sink(receiveValue: { (newTabs: [Tab]) in .sink(receiveValue: { (newTabs: [Tab]) in
@@ -207,7 +207,7 @@ class BrowserViewController: UIViewController
// Error button // Error button
toolbarController.urlBar.errorButton.addAction(UIAction(handler: { [unowned self] _ in toolbarController.urlBar.errorButton.addAction(UIAction(handler: { [unowned self] _ in
let alert = UIAlertController(title: "Error", message: self.tab.loadError?.localizedDescription, preferredStyle: .actionSheet) let alert = UIAlertController(title: "Error", message: currentTab.loadError?.localizedDescription, preferredStyle: .actionSheet)
alert.popoverPresentationController?.sourceView = self.toolbarController.urlBar.errorButton alert.popoverPresentationController?.sourceView = self.toolbarController.urlBar.errorButton
alert.addAction(UIAlertAction(title: "Reload", style: .destructive, handler: { _ in alert.addAction(UIAlertAction(title: "Reload", style: .destructive, handler: { _ in
@@ -240,7 +240,7 @@ class BrowserViewController: UIViewController
// Font size adjust // Font size adjust
toolbarController.urlBar.documentButton.addAction(UIAction(handler: { [unowned self] _ in toolbarController.urlBar.documentButton.addAction(UIAction(handler: { [unowned self] _ in
let documentControls = DocumentControlViewController(darkModeEnabled: tab.bridge.darkModeEnabled) let documentControls = DocumentControlViewController(darkModeEnabled: currentTab.bridge.darkModeEnabled)
documentControls.modalPresentationStyle = .popover documentControls.modalPresentationStyle = .popover
documentControls.popoverPresentationController?.permittedArrowDirections = [ .down, .up ] documentControls.popoverPresentationController?.permittedArrowDirections = [ .down, .up ]
documentControls.popoverPresentationController?.sourceView = toolbarController.urlBar.documentButton documentControls.popoverPresentationController?.sourceView = toolbarController.urlBar.documentButton
@@ -250,28 +250,30 @@ class BrowserViewController: UIViewController
numberFormatter.numberStyle = .percent numberFormatter.numberStyle = .percent
let label = documentControls.fontSizeAdjustView.labelView let label = documentControls.fontSizeAdjustView.labelView
label.text = numberFormatter.string(for: tab.webView._viewScale) label.text = numberFormatter.string(for: currentTab.webView._viewScale)
// Font size adjust // Font size adjust
documentControls.fontSizeAdjustView.decreaseSizeButton.addAction(UIAction(handler: { [unowned self] sender in documentControls.fontSizeAdjustView.decreaseSizeButton.addAction(UIAction(handler: { [unowned self] sender in
self.decreaseSize(sender) self.decreaseSize(sender)
label.text = numberFormatter.string(for: tab.webView._viewScale) label.text = numberFormatter.string(for: currentTab.webView._viewScale)
}), for: .touchUpInside) }), for: .touchUpInside)
documentControls.fontSizeAdjustView.increaseSizeButton.addAction(UIAction(handler: { [unowned self] sender in documentControls.fontSizeAdjustView.increaseSizeButton.addAction(UIAction(handler: { [unowned self] sender in
self.increaseSize(sender) self.increaseSize(sender)
label.text = numberFormatter.string(for: tab.webView._viewScale) label.text = numberFormatter.string(for: currentTab.webView._viewScale)
}), for: .touchUpInside) }), for: .touchUpInside)
// Find on page // Find on page
documentControls.findOnPageControlView.addAction(UIAction(handler: { [unowned self] _ in documentControls.findOnPageControlView.addAction(UIAction(handler: { [unowned self] _ in
documentControls.dismiss(animated: true, completion: nil) documentControls.dismiss(animated: true, completion: { [unowned self] in
// Needs to happen after dismissal, otherwise the find navigator will pick up the document
if FindOnPageViewController.isEnabled() { // controls as the divergent responder/deepest action responder (my bad...).
browserView.setFindOnPageVisible(true, animated: true) if FindOnPageViewController.isEnabled() {
} else if #available(iOS 16.0, *) { browserView.setFindOnPageVisible(true, animated: true)
browserView.webView?._findInteraction.presentFindNavigatorShowingReplace(false) } else if #available(iOS 16.0, *) {
} browserView.webView?.findInteraction?.presentFindNavigator(showingReplace: false)
}
})
}), for: .touchUpInside) }), for: .touchUpInside)
// Navigation controls // Navigation controls
@@ -331,6 +333,12 @@ class BrowserViewController: UIViewController
showShareSheetForCurrentURL(fromViewController: documentControls) showShareSheetForCurrentURL(fromViewController: documentControls)
}, for: .touchUpInside) }, for: .touchUpInside)
// History
documentControls.historyView.addAction(UIAction { [unowned self] action in
documentControls.dismiss(animated: false, completion: nil)
showHistory(action)
}, for: .touchUpInside)
present(documentControls, animated: true, completion: nil) present(documentControls, animated: true, completion: nil)
}), for: .touchUpInside) }), for: .touchUpInside)
@@ -340,8 +348,8 @@ class BrowserViewController: UIViewController
.sink(receiveValue: { [unowned self] (activeTab: Int) in .sink(receiveValue: { [unowned self] (activeTab: Int) in
if activeTab < tabController.tabs.count { if activeTab < tabController.tabs.count {
let tab = tabController.tabs[activeTab] let tab = tabController.tabs[activeTab]
if self.tab != tab { if currentTab != tab {
self.tab = tab currentTab = tab
} }
} }
@@ -349,6 +357,11 @@ class BrowserViewController: UIViewController
self.updateTabBarVisibility() self.updateTabBarVisibility()
}) })
registerForTraitChanges([UITraitHorizontalSizeClass.self]) { [weak self] (traitEnvironment: Self, previousTraitCollection) in
guard let self else { return }
toolbarController.reconfigureButtons(for: traitCollection.horizontalSizeClass)
}
self.view = browserView self.view = browserView
} }
@@ -362,7 +375,7 @@ class BrowserViewController: UIViewController
let shareableURL = ShareableURL( let shareableURL = ShareableURL(
url: url, url: url,
title: webView.title ?? url.absoluteString, title: webView.title ?? url.absoluteString,
favicon: tab.favicon favicon: currentTab.favicon
) )
let activityController = UIActivityViewController(activityItems: [ shareableURL ], applicationActivities: nil) let activityController = UIActivityViewController(activityItems: [ shareableURL ], applicationActivities: nil)
@@ -436,21 +449,21 @@ class BrowserViewController: UIViewController
} }
internal func showReaderWindow() { internal func showReaderWindow() {
tab.bridge.parseDocumentForReaderMode { string in currentTab.bridge.parseDocumentForReaderMode { [currentTab] string in
DispatchQueue.main.async { DispatchQueue.main.async {
if self.traitCollection.userInterfaceIdiom == .phone { if self.traitCollection.userInterfaceIdiom == .phone {
let readableViewController = ReaderViewController(readableHTMLString: string, baseURL: self.tab.bridge.webView.url) let readableViewController = ReaderViewController(readableHTMLString: string, baseURL: currentTab.bridge.webView.url)
readableViewController.title = self.tab.bridge.webView.title readableViewController.title = currentTab.bridge.webView.title
readableViewController.darkModeEnabled = self.tab.bridge.darkModeEnabled readableViewController.darkModeEnabled = currentTab.bridge.darkModeEnabled
readableViewController.delegate = self readableViewController.delegate = self
let navigationController = UINavigationController(rootViewController: readableViewController) let navigationController = UINavigationController(rootViewController: readableViewController)
self.present(navigationController, animated: true, completion: nil) self.present(navigationController, animated: true, completion: nil)
} else { } else {
let userActivity = NSUserActivity(activityType: SessionActivityType.ReaderWindow.rawValue) let userActivity = NSUserActivity(activityType: SessionActivityType.ReaderWindow.rawValue)
userActivity.title = self.tab.title userActivity.title = currentTab.title
userActivity.userInfo = [ userActivity.userInfo = [
ReaderUserActivityKeys.baseURL.rawValue : self.tab.bridge.webView.url ?? NSNull(), ReaderUserActivityKeys.baseURL.rawValue : currentTab.bridge.webView.url ?? NSNull(),
ReaderUserActivityKeys.htmlString.rawValue : string, ReaderUserActivityKeys.htmlString.rawValue : string,
] ]
let requestOptions = UIWindowScene.ActivationRequestOptions() let requestOptions = UIWindowScene.ActivationRequestOptions()
@@ -461,8 +474,41 @@ class BrowserViewController: UIViewController
} }
} }
internal func showHistoryWindow() {
let historyViewController = HistoryBrowserViewController { [unowned self] url in
if currentTab.url == nil {
currentTab.beginLoadingURL(url)
} else {
createNewTab(withURL: url)
}
presentedViewController?.dismiss(animated: true)
}
historyViewController.title = "History"
historyViewController.navigationItem.rightBarButtonItem = UIBarButtonItem(systemItem: .done, primaryAction: UIAction { _ in
// xxx: This is not the SwiftUI-y way to do this.
historyViewController.dismiss(animated: true)
})
historyViewController.navigationItem.leftBarButtonItem = UIBarButtonItem(title: "Clear", primaryAction: UIAction { [unowned self] action in
let alertController = UIAlertController(title: "Clear History", message: "Are you sure you want to clear all history?", preferredStyle: .actionSheet)
alertController.addAction(UIAlertAction(title: "Clear", style: .destructive, handler: { [unowned self] _ in
BrowserHistory.shared.clearAllHistory()
presentedViewController?.dismiss(animated: true)
}))
alertController.addAction(UIAlertAction(title: "Cancel", style: .cancel))
alertController.popoverPresentationController?.sourceItem = action.presentationSourceItem
presentedViewController?.present(alertController, animated: true)
})
let navigationController = UINavigationController(rootViewController: historyViewController)
present(navigationController, animated: true)
}
internal func updateLoadProgress(forWebView webView: WKWebView) { internal func updateLoadProgress(forWebView webView: WKWebView) {
if let loadError = tab.loadError { if let loadError = currentTab.loadError {
toolbarController.urlBar.loadProgress = .error(error: loadError) toolbarController.urlBar.loadProgress = .error(error: loadError)
} else if webView.estimatedProgress == 1.0 { } else if webView.estimatedProgress == 1.0 {
toolbarController.urlBar.loadProgress = .complete toolbarController.urlBar.loadProgress = .complete
@@ -474,6 +520,8 @@ class BrowserViewController: UIViewController
} }
internal func updateTitleAndURL(forWebView webView: WKWebView) { internal func updateTitleAndURL(forWebView webView: WKWebView) {
guard let tab = tabController.tab(forWebView: webView) else { return }
if webView == browserView.webView { if webView == browserView.webView {
browserView.titlebarView.setTitle(webView.title ?? "") browserView.titlebarView.setTitle(webView.title ?? "")
@@ -485,8 +533,7 @@ class BrowserViewController: UIViewController
} }
// Figure out which tab this corresponds to // Figure out which tab this corresponds to
let tab = tabController.tabs.first { $0.webView == webView } if let tabIndex = tabController.tabs.firstIndex(of: tab) {
if let tab = tab, let tabIndex = tabController.tabs.firstIndex(of: tab) {
tabBarViewController.tabBarView.reloadTab(atIndex: tabIndex) tabBarViewController.tabBarView.reloadTab(atIndex: tabIndex)
} }
} }
@@ -496,9 +543,9 @@ class BrowserViewController: UIViewController
tabController.activeTabIndex = activeIndex tabController.activeTabIndex = activeIndex
} }
tab.delegate = self currentTab.delegate = self
let webView = tab.webView let webView = currentTab.webView
webView.allowsBackForwardNavigationGestures = true webView.allowsBackForwardNavigationGestures = true
webView.navigationDelegate = self webView.navigationDelegate = self
webView.uiDelegate = self webView.uiDelegate = self
@@ -543,10 +590,23 @@ class BrowserViewController: UIViewController
updateScriptBlockerButton() updateScriptBlockerButton()
// Enforce dark mode setting // Enforce dark mode setting
tab.bridge.darkModeEnabled = toolbarController.darkModeEnabled currentTab.bridge.darkModeEnabled = toolbarController.darkModeEnabled
// Blur url bar, if applicable // Blur url bar, if applicable
toolbarController.urlBar.textField.resignFirstResponder() toolbarController.urlBar.textField.resignFirstResponder()
// If this tab's web content process was jettisoned while in background,
// its current page may appear blank and url be nil. Proactively reload
// when the tab becomes active again to restore state.
if tab.contentProcessTerminated {
if webView.url != nil || tab.url != nil {
webView.reload()
} else if let home = tab.homeURL {
tab.beginLoadingURL(home)
} else {
webView.reload()
}
}
} }
override func target(forAction action: Selector, withSender sender: Any?) -> Any? { override func target(forAction action: Selector, withSender sender: Any?) -> Any? {
@@ -571,18 +631,23 @@ class BrowserViewController: UIViewController
override func viewWillAppear(_ animated: Bool) { override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated) super.viewWillAppear(animated)
becomeFirstResponder() becomeFirstResponder()
}
override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) { // If the app resumed and the current tab's web content process was
super.traitCollectionDidChange(previousTraitCollection) // terminated while in background, ensure we restore it now.
if currentTab.contentProcessTerminated {
// Not sure why this doesn't happen automatically... if webView.url != nil || currentTab.url != nil {
toolbarController.traitCollectionDidChange(previousTraitCollection) webView.reload()
} else if let home = currentTab.homeURL {
currentTab.beginLoadingURL(home)
} else {
webView.reload()
}
}
} }
internal func updateScriptBlockerButton() { internal func updateScriptBlockerButton() {
var numBlockedScripts: Int = tab.blockedScriptOrigins.count var numBlockedScripts: Int = currentTab.blockedScriptOrigins.count
if tab.url != nil, tab.javaScriptEnabled == false { if currentTab.url != nil, currentTab.javaScriptEnabled == false {
// Because the page is blocked too, notify. // Because the page is blocked too, notify.
numBlockedScripts += 1 numBlockedScripts += 1
} }
@@ -593,7 +658,7 @@ class BrowserViewController: UIViewController
} }
let iconView = toolbarController.scriptControllerIconView let iconView = toolbarController.scriptControllerIconView
iconView.shieldsDown = tab.javaScriptEnabled iconView.shieldsDown = currentTab.javaScriptEnabled
// iconView.setBlockedScriptsNumber(numBlockedScripts) // iconView.setBlockedScriptsNumber(numBlockedScripts)
if let policy = policy { if let policy = policy {
@@ -608,7 +673,7 @@ class BrowserViewController: UIViewController
let newTab = tabController.createNewTab(url: url) let newTab = tabController.createNewTab(url: url)
if !loadInBackground { if !loadInBackground {
self.tab = newTab currentTab = newTab
if url == nil && traitCollection.userInterfaceIdiom == .mac { if url == nil && traitCollection.userInterfaceIdiom == .mac {
self.toolbarController.urlBar.textField.becomeFirstResponder() self.toolbarController.urlBar.textField.becomeFirstResponder()
@@ -616,7 +681,7 @@ class BrowserViewController: UIViewController
} else { } else {
// Send this message to get it to load NOW, instead of waiting for it to show up // Send this message to get it to load NOW, instead of waiting for it to show up
// in the view hierarchy. // in the view hierarchy.
tab.webView.didMoveToWindow() currentTab.webView.didMoveToWindow()
// Update tab bar now // Update tab bar now
updateTabBarVisibility() updateTabBarVisibility()
@@ -637,12 +702,12 @@ extension BrowserViewController: UIPopoverPresentationControllerDelegate
extension BrowserViewController: ScriptPolicyViewControllerDelegate extension BrowserViewController: ScriptPolicyViewControllerDelegate
{ {
func didChangeScriptPolicy() { func didChangeScriptPolicy() {
tab.bridge.policyDataSourceDidChange() currentTab.bridge.policyDataSourceDidChange()
webView.reload() webView.reload()
} }
func setScriptsEnabledForTab(_ enabled: Bool) { func setScriptsEnabledForTab(_ enabled: Bool) {
tab.javaScriptEnabled = enabled currentTab.javaScriptEnabled = enabled
toolbarController.scriptControllerIconView.shieldsDown = enabled toolbarController.scriptControllerIconView.shieldsDown = enabled
} }
} }
@@ -650,7 +715,7 @@ extension BrowserViewController: ScriptPolicyViewControllerDelegate
extension BrowserViewController: AutocompleteViewControllerDelegate extension BrowserViewController: AutocompleteViewControllerDelegate
{ {
func autocompleteController(_: AutocompleteViewController, didSelectHistoryItem item: HistoryItem) { func autocompleteController(_: AutocompleteViewController, didSelectHistoryItem item: HistoryItem) {
tab.beginLoadingURL(item.url) currentTab.beginLoadingURL(item.url)
autocompleteViewController.view.isHidden = true autocompleteViewController.view.isHidden = true
} }
} }
@@ -665,7 +730,7 @@ extension BrowserViewController: TabDelegate
extension BrowserViewController: TabPickerViewControllerDelegate extension BrowserViewController: TabPickerViewControllerDelegate
{ {
func tabPicker(_ picker: TabPickerViewController, createNewTabWithURL url: URL?) { func tabPicker(_ picker: TabPickerViewController, createNewTabWithURL url: URL?) {
self.tab = tabController.createNewTab(url: url) currentTab = tabController.createNewTab(url: url)
picker.dismiss(animated: true) picker.dismiss(animated: true)
} }
@@ -684,7 +749,7 @@ extension BrowserViewController: TabPickerViewControllerDelegate
guard let tab else { return } guard let tab else { return }
self.tab = tab currentTab = tab
picker.dismiss(animated: true, completion: nil) picker.dismiss(animated: true, completion: nil)
} }
@@ -694,7 +759,7 @@ extension BrowserViewController: TabPickerViewControllerDelegate
tabController.closeTab(tab) tabController.closeTab(tab)
// Dismiss picker if current tab is closed using the picker // Dismiss picker if current tab is closed using the picker
if tab == self.tab { if tab == currentTab {
picker.dismiss(animated: true, completion: nil) picker.dismiss(animated: true, completion: nil)
} }
} }
@@ -728,10 +793,10 @@ extension BrowserViewController: UITextFieldDelegate
} }
} }
tab.beginLoadingURL(url) currentTab.beginLoadingURL(url)
} else { } else {
let searchURL = Settings.shared.searchProvider.provider().searchURLWithQuery(text) let searchURL = Settings.shared.currentSearchProvider().searchURLWithQuery(text)
tab.beginLoadingURL(searchURL) currentTab.beginLoadingURL(searchURL)
} }
textField.resignFirstResponder() textField.resignFirstResponder()
@@ -740,10 +805,6 @@ extension BrowserViewController: UITextFieldDelegate
return false return false
} }
override func textFieldShouldEndEditing(_ textField: UITextField) -> Bool {
true
}
func textFieldDidEndEditing(_ textField: UITextField) { func textFieldDidEndEditing(_ textField: UITextField) {
if !changingFocusToAutocompleteController { if !changingFocusToAutocompleteController {
autocompleteViewController.view.isHidden = true autocompleteViewController.view.isHidden = true
@@ -754,7 +815,7 @@ extension BrowserViewController: UITextFieldDelegate
extension BrowserViewController: ReaderViewControllerDelegate extension BrowserViewController: ReaderViewControllerDelegate
{ {
func readerViewController(_ reader: ReaderViewController, didRequestNavigationToURL navigationURL: URL) { func readerViewController(_ reader: ReaderViewController, didRequestNavigationToURL navigationURL: URL) {
tab.beginLoadingURL(navigationURL) currentTab.beginLoadingURL(navigationURL)
} }
} }
+7 -9
View File
@@ -28,11 +28,17 @@ class GradientView: UIImageView
self.init(image: nil) self.init(image: nil)
self.direction = direction self.direction = direction
self.colors = colors self.colors = colors
registerForTraitChanges([UITraitUserInterfaceStyle.self]) { [weak self] (traitEnvironment: Self, previousTraitCollection) in
self?.image = nil
self?.setNeedsLayout()
}
} }
private func gradientImage(forSize size: CGSize) -> UIImage? { private func gradientImage(forSize size: CGSize) -> UIImage? {
var image: UIImage? = nil guard size.width > .leastNonzeroMagnitude && size.height > .leastNonzeroMagnitude else { return nil }
var image: UIImage? = nil
UIGraphicsBeginImageContext(size) UIGraphicsBeginImageContext(size)
if let context = UIGraphicsGetCurrentContext() { if let context = UIGraphicsGetCurrentContext() {
let gradientColorsArray = self.colors.map { $0.cgColor } let gradientColorsArray = self.colors.map { $0.cgColor }
@@ -69,12 +75,4 @@ class GradientView: UIImageView
generatedImageSize = bounds.size generatedImageSize = bounds.size
} }
} }
override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
super.traitCollectionDidChange(previousTraitCollection)
// Regenerate image
image = nil
setNeedsLayout()
}
} }
+7 -5
View File
@@ -46,6 +46,8 @@ class ReliefButton: UIButton
} }
}) })
self.imageView?.contentMode = .scaleAspectFit
shadowView.alpha = 0.28 shadowView.alpha = 0.28
shadowView.backgroundColor = UIColor(white: 0.0, alpha: 1.0) shadowView.backgroundColor = UIColor(white: 0.0, alpha: 1.0)
shadowView.isUserInteractionEnabled = false shadowView.isUserInteractionEnabled = false
@@ -67,12 +69,14 @@ class ReliefButton: UIButton
backgroundView.layer.rasterizationScale = UIScreen.main.scale backgroundView.layer.rasterizationScale = UIScreen.main.scale
addSubview(backgroundView) addSubview(backgroundView)
traitCollectionDidChange(nil) registerForTraitChanges([UITraitUserInterfaceStyle.self], handler: handleTraitCollectionChange)
pointerStyleProvider = { (button, pointerEffect, pointerShape) -> UIPointerStyle? in pointerStyleProvider = { (button, pointerEffect, pointerShape) -> UIPointerStyle? in
let preview = UITargetedPreview(view: button) let preview = UITargetedPreview(view: button)
return UIPointerStyle(effect: .lift(preview), shape: pointerShape) return UIPointerStyle(effect: .lift(preview), shape: pointerShape)
} }
handleTraitCollectionChange(self, previousCollection: nil)
} }
required init?(coder: NSCoder) { required init?(coder: NSCoder) {
@@ -97,7 +101,7 @@ class ReliefButton: UIButton
return inverted ? colors.reversed() : colors return inverted ? colors.reversed() : colors
} }
override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) { private func handleTraitCollectionChange(_ environment: UITraitEnvironment, previousCollection: UITraitCollection?) {
setBackgroundInverted(isHighlighted) setBackgroundInverted(isHighlighted)
backgroundView.layer.borderColor = { traitCollection -> UIColor in backgroundView.layer.borderColor = { traitCollection -> UIColor in
if traitCollection.userInterfaceStyle == .dark { if traitCollection.userInterfaceStyle == .dark {
@@ -105,7 +109,7 @@ class ReliefButton: UIButton
} else { } else {
return .white return .white
} }
}(traitCollection).cgColor } (traitCollection).cgColor
} }
internal func setBackgroundInverted(_ inverted: Bool) { internal func setBackgroundInverted(_ inverted: Bool) {
@@ -132,8 +136,6 @@ class ReliefButton: UIButton
} }
override func layoutSubviews() { override func layoutSubviews() {
self.imageView?.contentMode = .scaleAspectFit
super.layoutSubviews() super.layoutSubviews()
sendSubviewToBack(backgroundView) sendSubviewToBack(backgroundView)
@@ -103,9 +103,16 @@ class DocumentControlItemView: UIControl
separatorView.isHidden = !drawsBottomSeparator separatorView.isHidden = !drawsBottomSeparator
} }
override func setTracking(_ tracking: Bool) { override func beginTracking(_ touch: UITouch, with event: UIEvent?) -> Bool {
super.setTracking(tracking) super.beginTracking(touch, with: event)
highlightView.isHidden = !tracking highlightView.isHidden = false
return true
}
override func endTracking(_ touch: UITouch?, with event: UIEvent?) {
super.endTracking(touch, with: event)
highlightView.isHidden = true
} }
public func title(_ title: String) -> Self { public func title(_ title: String) -> Self {
@@ -20,6 +20,7 @@ class DocumentControlViewController: UIViewController
let archiveView = DocumentControlItemView().title("Archive.today") .symbol("shippingbox") let archiveView = DocumentControlItemView().title("Archive.today") .symbol("shippingbox")
let emailView = DocumentControlItemView().title("Email") .symbol("envelope") let emailView = DocumentControlItemView().title("Email") .symbol("envelope")
let sharingView = DocumentControlItemView().title("Share") .symbol("square.and.arrow.up") let sharingView = DocumentControlItemView().title("Share") .symbol("square.and.arrow.up")
let historyView = DocumentControlItemView().title("History") .symbol("clock.arrow.circlepath")
let darkModeView = DocumentControlItemView().title("Dark Mode") let darkModeView = DocumentControlItemView().title("Dark Mode")
var observations: [NSKeyValueObservation] = [] var observations: [NSKeyValueObservation] = []
@@ -46,6 +47,7 @@ class DocumentControlViewController: UIViewController
documentControlsView.stackView.addArrangedSubview(darkModeView) documentControlsView.stackView.addArrangedSubview(darkModeView)
documentControlsView.stackView.addArrangedSubview(readabilityView) documentControlsView.stackView.addArrangedSubview(readabilityView)
documentControlsView.stackView.addArrangedSubview(archiveView) documentControlsView.stackView.addArrangedSubview(archiveView)
documentControlsView.stackView.addArrangedSubview(historyView)
documentControlsView.stackView.addArrangedSubview(settingsView) documentControlsView.stackView.addArrangedSubview(settingsView)
@@ -41,7 +41,7 @@ class FindOnPageViewController: UIViewController, _WKFindDelegate
findOnPageView.textField.addAction(UIAction(handler: { [unowned self] _ in findOnPageView.textField.addAction(UIAction(handler: { [unowned self] _ in
self.findString = findOnPageView.textField.text self.findString = findOnPageView.textField.text
webView?._find(self.findString, options: self.findOptions, maxCount: self.maxCount) webView?._find(self.findString, options: self.findOptions, maxCount: UInt(self.maxCount))
}), for: .editingChanged) }), for: .editingChanged)
findOnPageView.prevResultButton.addAction(UIAction(handler: { [unowned self] _ in findOnPageView.prevResultButton.addAction(UIAction(handler: { [unowned self] _ in
@@ -82,24 +82,20 @@ class FindOnPageViewController: UIViewController, _WKFindDelegate
@objc @objc
override func findNext(_ sender: Any?) { override func findNext(_ sender: Any?) {
webView?._find(self.findString, options: self.findOptions, maxCount: self.maxCount) webView?._find(self.findString, options: self.findOptions, maxCount: UInt(self.maxCount))
} }
@objc @objc
override func findPrevious(_ sender: Any?) { override func findPrevious(_ sender: Any?) {
let options: _WKFindOptions = self.findOptions.union(.backwards) let options: _WKFindOptions = self.findOptions.union(.backwards)
webView?._find(self.findString, options: options, maxCount: self.maxCount) webView?._find(self.findString, options: options, maxCount: UInt(self.maxCount))
} }
func _webView(_ webView: WKWebView!, didFailToFind string: String!) { func _webView(_ webView: WKWebView!, didFailToFind string: String!) {
// ?? // ??
} }
func _webView(_ webView: WKWebView!, didCountMatches matches: UInt, for string: String!) { internal func _webView(_ webView: WKWebView!, didFindMatches matches: UInt, for string: String!, withMatch matchIndex: Int) {
// TODO: Update a label
}
func _webView(_ webView: WKWebView!, didFindMatches matches: UInt, for string: String!, withMatch matchIndex: Int) {
findOnPageView.nextResultButton.isEnabled = matches > 0 findOnPageView.nextResultButton.isEnabled = matches > 0
findOnPageView.prevResultButton.isEnabled = matches > 0 findOnPageView.prevResultButton.isEnabled = matches > 0
} }
+12
View File
@@ -0,0 +1,12 @@
//
// Hacks.h
// App
//
// Created by James Magahern on 5/5/25.
//
#import <Foundation/Foundation.h>
extern Class DecodedClass(const char *encodedClassName);
extern SEL DecodedSelector(const char *encodedSelectorName);
extern void SwizzleClassMethod(Class targetClass, SEL originalSelector, IMP replacementIMP);
+31
View File
@@ -0,0 +1,31 @@
//
// Hacks.m
// App
//
// Created by James Magahern on 5/5/25.
//
#import "Hacks.h"
#import <objc/runtime.h>
Class DecodedClass(const char *encodedClassName) {
NSData *data = [NSData dataWithBytesNoCopy:(void *)encodedClassName length:strlen(encodedClassName) freeWhenDone:NO];
data = [[NSData alloc] initWithBase64EncodedData:data options:0];
NSString *str = [[NSString alloc] initWithData:data encoding:NSASCIIStringEncoding];
return NSClassFromString(str);
}
SEL DecodedSelector(const char *encodedSelectorName) {
NSData *data = [NSData dataWithBytesNoCopy:(void *)encodedSelectorName length:strlen(encodedSelectorName) freeWhenDone:NO];
data = [[NSData alloc] initWithBase64EncodedData:data options:0];
NSString *str = [[NSString alloc] initWithData:data encoding:NSASCIIStringEncoding];
return NSSelectorFromString(str);
}
void SwizzleClassMethod(Class targetClass, SEL originalSelector, IMP replacementIMP)
{
Method originalMethod = class_getClassMethod(targetClass, originalSelector);
method_setImplementation(originalMethod, replacementIMP);
}
+29 -13
View File
@@ -7,27 +7,43 @@
#import <CoreFoundation/CoreFoundation.h> #import <CoreFoundation/CoreFoundation.h>
#import <MessageUI/MessageUI.h> #import <MessageUI/MessageUI.h>
#import <WebKit/WKWebView.h>
#import <objc/runtime.h> #import <objc/runtime.h>
#import "Hacks.h"
@interface MFMailComposeViewController (InternalMethods)
+ (BOOL)canSendMailSourceAccountManagement:(NSInteger)sourceAccountManagement;
@end
static void swizzleClassMethod(Class targetClass, SEL originalSelector, IMP replacementIMP)
{
Method originalMethod = class_getClassMethod(targetClass, originalSelector);
method_setImplementation(originalMethod, replacementIMP);
}
__attribute__((constructor)) __attribute__((constructor))
static void swizzleMFMailComposeViewController() static void swizzleMFMailComposeViewController(void)
{ {
// Hacks to work around the fact that MFMailComposeViewController is broken in iOS 14+ // Hacks to work around the fact that MFMailComposeViewController is broken in iOS 14+
swizzleClassMethod(MFMailComposeViewController.class, @selector(canSendMail), imp_implementationWithBlock(^BOOL(void) { SwizzleClassMethod(MFMailComposeViewController.class, @selector(canSendMail), imp_implementationWithBlock(^BOOL(void) {
return YES; return YES;
})); }));
swizzleClassMethod(MFMailComposeViewController.class, @selector(canSendMailSourceAccountManagement:), imp_implementationWithBlock(^BOOL(NSInteger sam) { SEL canSendMailSourceAccountManagement = DecodedSelector("Y2FuU2VuZE1haWxTb3VyY2VBY2NvdW50TWFuYWdlbWVudDo=");
SwizzleClassMethod(MFMailComposeViewController.class, canSendMailSourceAccountManagement, imp_implementationWithBlock(^BOOL(NSInteger sam) {
return YES; return YES;
})); }));
} }
@interface WKWebView (Hacks)
+ (BOOL)orig_handlesURLScheme:(NSString *)scheme;
@end
@implementation WKWebView (Hacks)
+ (BOOL)orig_handlesURLScheme:(NSString *)scheme
{
if ([scheme isEqualToString:@"http"] || [scheme isEqualToString:@"https"]) {
return NO;
}
return [self orig_handlesURLScheme:scheme];
}
@end
__attribute__((constructor))
static void swizzleWebKitConfiguration(void)
{
Method origMethod = class_getClassMethod(WKWebView.class, @selector(handlesURLScheme:));
Method replMethod = class_getClassMethod(WKWebView.class, @selector(orig_handlesURLScheme:));
method_exchangeImplementations(origMethod, replMethod);
}
@@ -0,0 +1,20 @@
//
// HistoryBrowserViewController.swift
// App
//
// Created by James Magahern on 1/20/23.
//
import SwiftUI
import UIKit
@MainActor
class HistoryBrowserViewController: UIHostingController<HistoryView> {
public init(onSelectItem: @escaping (URL) -> Void) {
super.init(rootView: HistoryView(viewModel: BrowserHistory.shared.viewModel(limit: 500), onSelectItem: onSelectItem))
}
required dynamic init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
+78
View File
@@ -0,0 +1,78 @@
//
// HistoryView.swift
// App
//
// Created by James Magahern on 1/20/23.
//
import SwiftUI
import UniformTypeIdentifiers
struct HistoryView: View {
@StateObject public var viewModel: BrowserHistory.ViewModel
@State public var selectedItems = Set<HistoryItem.ID>()
public var onSelectItem: (URL) -> Void
private let dateFormatter = DateFormatter() .. {
$0.locale = Locale.current
$0.dateStyle = .medium
$0.timeStyle = .short
}
@Environment(\.dismiss) private var dismissAction
@Environment(\.horizontalSizeClass) private var horizontalSizeClass
var body: some View {
Table(viewModel.historyItems, selection: $selectedItems) {
TableColumn("Title") { item in
VStack(alignment: .leading) {
Text(item.title.count > 0 ? item.title : item.url.absoluteString)
.lineLimit(1)
if horizontalSizeClass == .compact {
Text(item.url.shortString()).font(.caption).lineLimit(1)
Text(dateFormatter.string(from: item.lastVisited))
.font(.caption)
.foregroundColor(.secondary)
}
}
}
TableColumn("URL", value: \.url.absoluteString)
TableColumn("Last Visited") { item in
Text(dateFormatter.string(from: item.lastVisited))
}
}
.contextMenu(forSelectionType: HistoryItem.ID.self, menu: { items in
let historyItems = items.compactMap { viewModel.item(forIdentifier: $0) }
Button("Copy") {
UIPasteboard.general.setItems(historyItems.map { [
UTType.url.identifier : $0.url,
UTType.utf8PlainText.identifier : $0.url.absoluteString,
] })
}
Button("Delete") {
viewModel.deleteItems(items)
}
}, primaryAction: { items in
items.compactMap({ viewModel.item(forIdentifier: $0) }).forEach { item in
onSelectItem(item.url)
}
})
.searchable(text: $viewModel.searchQuery)
}
}
struct HistoryViewPreviewProvider: PreviewProvider {
static var previews: some View {
HistoryView(viewModel: BrowserHistory.shared.viewModel(), onSelectItem: { _ in })
.previewLayout(.fixed(width: 480.0, height: 800.0))
}
}
extension URL {
public func shortString() -> String {
return String(absoluteString.trimmingPrefix(try! Regex("(.+)://")))
}
}
+62
View File
@@ -50,6 +50,28 @@ protocol ShortcutResponder: AnyObject {
@objc @objc
optional func openInReaderMode(_ sender: Any?) optional func openInReaderMode(_ sender: Any?)
@objc
optional func showHistory(_ sender: Any?)
@objc
optional func handleOpenURL(_ sender: Any?, forEvent event: OpenURLEvent?)
@objc
optional func lowerScriptPolicyRestriction(_ sender: Any?)
@objc
optional func raiseScriptPolicyRestriction(_ sender: Any?)
@objc
optional func zoomToActualSize(_ sender: Any?)
}
public class OpenURLEvent: UIEvent {
let url: URL
public init(url: URL) {
self.url = url
}
} }
fileprivate extension Array { fileprivate extension Array {
@@ -140,6 +162,22 @@ public class KeyboardShortcuts {
action: #selector(ShortcutResponder.stop) action: #selector(ShortcutResponder.stop)
) )
]), ]),
UIMenu(options: .displayInline, children: [
// Raise Script Policy Restriction
UIKeyCommand(
modifiers: [.alternate], input: "x",
title: "Raise Script Policy Restriction",
action: #selector(ShortcutResponder.raiseScriptPolicyRestriction)
),
// Lower Script Policy Restriction
UIKeyCommand(
modifiers: [.alternate], input: "c",
title: "Lower Script Policy Restriction",
action: #selector(ShortcutResponder.lowerScriptPolicyRestriction)
),
])
].removeNulls() ].removeNulls()
case .go: case .go:
@@ -188,6 +226,30 @@ public class KeyboardShortcuts {
case .view: case .view:
return [ return [
// Zoom
UIMenu(options: .displayInline, children: [
// Actual Size
UIKeyCommand(
modifiers: .command, input: "0",
title: "Actual Size",
action: #selector(ShortcutResponder.zoomToActualSize)
),
// Increase Zoom
UIKeyCommand(
modifiers: .command, input: "=",
title: "Zoom In",
action: #selector(UIResponder.increaseSize)
),
// Go Forward
UIKeyCommand(
modifiers: .command, input: "-",
title: "Zoom Out",
action: #selector(UIResponder.decreaseSize)
),
]),
// Toggle Dark Mode // Toggle Dark Mode
UIKeyCommand( UIKeyCommand(
modifiers: [.command], input: "M", modifiers: [.command], input: "M",
-1
View File
@@ -8,7 +8,6 @@ html, iframe {
-webkit-filter: invert(100%) !important; -webkit-filter: invert(100%) !important;
} }
em,
img, img,
svg, svg,
form, form,
@@ -30,7 +30,7 @@ class BrowserSceneDelegate: UIResponder, UIWindowSceneDelegate {
if let urlContext = connectionOptions.urlContexts.first { if let urlContext = connectionOptions.urlContexts.first {
let url = urlContext.url let url = urlContext.url
browserViewController.tab.beginLoadingURL(url) browserViewController.currentTab.beginLoadingURL(url)
} }
#if targetEnvironment(macCatalyst) #if targetEnvironment(macCatalyst)
@@ -59,8 +59,8 @@ class BrowserSceneDelegate: UIResponder, UIWindowSceneDelegate {
func scene(_ scene: UIScene, openURLContexts URLContexts: Set<UIOpenURLContext>) func scene(_ scene: UIScene, openURLContexts URLContexts: Set<UIOpenURLContext>)
{ {
for urlContext in URLContexts { for urlContext in URLContexts {
if browserViewController?.tab.url == nil { if browserViewController?.currentTab.url == nil {
browserViewController?.tab.beginLoadingURL(urlContext.url) browserViewController?.currentTab.beginLoadingURL(urlContext.url)
} else { } else {
browserViewController?.createNewTab(withURL: urlContext.url) browserViewController?.createNewTab(withURL: urlContext.url)
} }
@@ -0,0 +1,74 @@
//
// AddSearchEngineViewController.swift
// App
//
// Created by James Magahern on 9/29/25.
//
import SwiftUI
import UIKit
@MainActor
struct AddSearchEngineView: View
{
@State var model: ViewModel
var body: some View {
Form {
Section {
HStack {
Text("Name")
TextField("Name", text: $model.name)
.multilineTextAlignment(.trailing)
.autocorrectionDisabled()
}
}
Section {
HStack {
Text("URL")
TextField("https://example.com/query=%q", text: $model.url)
.multilineTextAlignment(.trailing)
.autocorrectionDisabled()
.textContentType(.none)
}
} footer: {
Text("URL must contain %q, which will be replaced by the query. ")
}
Section {
Toggle("Set as default", isOn: $model.makeDefault)
}
}
.navigationTitle("Add Search Engine")
}
@MainActor
@Observable
class ViewModel
{
var name: String = ""
var url: String = ""
var makeDefault: Bool = false
}
}
@MainActor
class AddSearchEngineViewController: UIHostingController<AddSearchEngineView>
{
public var viewModel: AddSearchEngineView.ViewModel
init() {
self.viewModel = AddSearchEngineView.ViewModel()
super.init(rootView: AddSearchEngineView(model: viewModel))
}
@MainActor @preconcurrency required dynamic init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
#Preview {
@Previewable @State var model = AddSearchEngineView.ViewModel()
AddSearchEngineView(model: model)
}
+6 -6
View File
@@ -23,8 +23,8 @@ struct AmberSettingsView: View {
@Environment(\.presentationMode) @Environment(\.presentationMode)
@Binding private var presentationMode @Binding private var presentationMode
@State private var searchProvider = Settings.shared.searchProvider { @State private var defaultSearchEngineName = Settings.shared.defaultSearchEngineName {
didSet { Settings.shared.searchProvider = searchProvider } didSet { Settings.shared.defaultSearchEngineName = defaultSearchEngineName }
} }
var body: some View { var body: some View {
@@ -35,12 +35,12 @@ struct AmberSettingsView: View {
}) })
Section(header: Text("Search Provider"), content: { Section(header: Text("Search Provider"), content: {
ForEach(Settings.SearchProviderSetting.allCases, id: \.self, content: { setting in ForEach(Array(Settings.shared.searchEngines.keys).sorted(), id: \.self, content: { name in
Button(action: { searchProvider = setting }, label: { Button(action: { defaultSearchEngineName = name }, label: {
HStack { HStack {
Text(setting.rawValue) Text(name)
Spacer() Spacer()
if searchProvider == setting { if defaultSearchEngineName == name {
Image(systemName: "checkmark") Image(systemName: "checkmark")
} }
} }
+191 -56
View File
@@ -32,18 +32,45 @@ struct TextFieldContentConfiguration : UIContentConfiguration
{ {
var text: String = "" var text: String = ""
var placeholderText: String? = nil var placeholderText: String? = nil
var textChanged: ((String) -> Void) var textChanged: ((String) -> Void)? = nil
var pressedReturn: ((UITextField) -> Void)? = nil
var keyboardType: UIKeyboardType = .default
var returnKeyType: UIReturnKeyType = .default
func makeContentView() -> UIView & UIContentView { func makeContentView() -> UIView & UIContentView {
let textField = UITextField(frame: .zero) let textField = UITextField(frame: .zero)
textField.borderStyle = .roundedRect textField.borderStyle = .roundedRect
textField.autocorrectionType = .no textField.autocorrectionType = .no
textField.autocapitalizationType = .none textField.autocapitalizationType = .none
textField.keyboardType = keyboardType
textField.returnKeyType = returnKeyType
return GenericContentView<UITextField, TextFieldContentConfiguration>(configuration: self, view: textField) { config, textField in return GenericContentView<UITextField, TextFieldContentConfiguration>(configuration: self, view: textField) { config, textField in
textField.text = config.text textField.text = config.text
textField.placeholder = config.placeholderText textField.placeholder = config.placeholderText
textField.addAction(UIAction { _ in config.textChanged(textField.text ?? "") }, for: .editingChanged)
if let textChanged = config.textChanged {
textField.addAction(UIAction { _ in textChanged(textField.text ?? "") }, for: .editingChanged)
}
if let pressedReturn = config.pressedReturn {
class ReturnDelegate : NSObject, UITextFieldDelegate {
var pressedReturn: ((UITextField) -> Void)
public init(pressedReturn: @escaping ((UITextField) -> Void)) {
self.pressedReturn = pressedReturn
}
public func textFieldShouldReturn(_ textField: UITextField) -> Bool {
pressedReturn(textField)
return false
}
}
let delegate = ReturnDelegate(pressedReturn: pressedReturn)
objc_setAssociatedObject(textField, "returnDelegate", delegate, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
textField.delegate = delegate
}
} }
} }
@@ -90,45 +117,46 @@ class GeneralSettingsViewController: UIViewController
let dataSource: UICollectionViewDiffableDataSource<Section, Item> let dataSource: UICollectionViewDiffableDataSource<Section, Item>
let collectionView: UICollectionView let collectionView: UICollectionView
let viewModel: ViewModel
static func createLayout(forIdiom idiom: UIUserInterfaceIdiom) -> UICollectionViewLayout { static func createLayout(forIdiom idiom: UIUserInterfaceIdiom) -> UICollectionViewLayout {
#if targetEnvironment(macCatalyst) #if targetEnvironment(macCatalyst)
let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0),
heightDimension: .fractionalHeight(1.0)) heightDimension: .fractionalHeight(1.0))
let item = NSCollectionLayoutItem(layoutSize: itemSize) let item = NSCollectionLayoutItem(layoutSize: itemSize)
let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(0.60), let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(0.60),
heightDimension: .absolute(44)) heightDimension: .absolute(44))
let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitem: item, count: 1) let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitem: item, count: 1)
group.edgeSpacing = NSCollectionLayoutEdgeSpacing(leading: .flexible(1.0), top: nil, trailing: nil, bottom: nil) group.edgeSpacing = NSCollectionLayoutEdgeSpacing(leading: .flexible(1.0), top: nil, trailing: nil, bottom: nil)
let insets = NSDirectionalEdgeInsets(top: 24.0, leading: 64.0, bottom: 24.0, trailing: 64.0) let insets = NSDirectionalEdgeInsets(top: 24.0, leading: 64.0, bottom: 24.0, trailing: 64.0)
let headerFooterSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(0.40), let headerFooterSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(0.40),
heightDimension: .estimated(44.0)) heightDimension: .estimated(44.0))
let sectionHeader = NSCollectionLayoutBoundarySupplementaryItem( let sectionHeader = NSCollectionLayoutBoundarySupplementaryItem(
layoutSize: headerFooterSize, layoutSize: headerFooterSize,
elementKind: UICollectionView.elementKindSectionHeader, elementKind: UICollectionView.elementKindSectionHeader,
alignment: .topLeading, alignment: .topLeading,
absoluteOffset: CGPoint(x: insets.leading, y: insets.top) absoluteOffset: CGPoint(x: insets.leading, y: insets.top + 5.0)
) )
sectionHeader.extendsBoundary = false sectionHeader.extendsBoundary = false
sectionHeader.contentInsets = insets sectionHeader.contentInsets = insets
let section = NSCollectionLayoutSection(group: group) let section = NSCollectionLayoutSection(group: group)
// section.interGroupSpacing = spacing // section.interGroupSpacing = spacing
section.contentInsets = insets section.contentInsets = insets
section.supplementariesFollowContentInsets = true section.supplementariesFollowContentInsets = true
section.boundarySupplementaryItems = [ sectionHeader ] section.boundarySupplementaryItems = [ sectionHeader ]
let layout = UICollectionViewCompositionalLayout(section: section) let layout = UICollectionViewCompositionalLayout(section: section)
return layout return layout
#else #else
var listConfiguration = UICollectionLayoutListConfiguration(appearance: .insetGrouped) var listConfiguration = UICollectionLayoutListConfiguration(appearance: .insetGrouped)
listConfiguration.headerMode = .supplementary listConfiguration.headerMode = .supplementary
return UICollectionViewCompositionalLayout.list(using: listConfiguration) return UICollectionViewCompositionalLayout.list(using: listConfiguration)
#endif #endif
} }
static func sectionHeaderConfiguration(forIdiom idiom: UIUserInterfaceIdiom, sectionName: String) -> UIContentConfiguration { static func sectionHeaderConfiguration(forIdiom idiom: UIUserInterfaceIdiom, sectionName: String) -> UIContentConfiguration {
@@ -139,33 +167,41 @@ class GeneralSettingsViewController: UIViewController
textAlignment: .right textAlignment: .right
) )
} else { } else {
var config = UIListContentConfiguration.plainHeader() var config = UIListContentConfiguration.header()
config.text = sectionName config.text = sectionName
return config return config
} }
} }
#if targetEnvironment(macCatalyst) #if targetEnvironment(macCatalyst)
static let staticIdiom = UIUserInterfaceIdiom.mac static let staticIdiom = UIUserInterfaceIdiom.mac
#else #else
static let staticIdiom = UIUserInterfaceIdiom.pad static let staticIdiom = UIUserInterfaceIdiom.pad
#endif #endif
init() { init() {
let actionHandler = { (action: UIAction) in let actionHandler = { (action: UIAction) in
let providerString = action.title let engineName = action.title
let provider = Settings.SearchProviderSetting(rawValue: providerString)! Settings.shared.defaultSearchEngineName = engineName
Settings.shared.searchProvider = provider
} }
let viewModel = ViewModel()
let itemCellRegistry = UICollectionView.CellRegistration<UICollectionViewCell, Item> { cell, indexPath, identifier in let itemCellRegistry = UICollectionView.CellRegistration<UICollectionViewCell, Item> { cell, indexPath, identifier in
if identifier == Self.SearchProviderPopupItem { if identifier == Self.SearchProviderPopupItem {
let menu = UIMenu(children: Settings.SearchProviderSetting.allCases.map { provider in let names = Settings.shared.searchEngines.keys.sorted()
let action = UIAction(title: provider.rawValue, handler: actionHandler) var engineMenuItems: [UIMenuElement] = names.map { name in
action.state = Settings.shared.searchProvider == provider ? .on : .off let action = UIAction(title: name, handler: actionHandler)
action.state = (Settings.shared.defaultSearchEngineName == name) ? .on : .off
return action return action
}) }
engineMenuItems.append(UIMenu(options: .displayInline, children: [
UIAction(title: "Manage Search Engines…", handler: { _ in
viewModel.onManageSearchEngines()
})
]))
let menu = UIMenu(children: engineMenuItems)
cell.contentConfiguration = ButtonContentConfiguration(menu: menu) cell.contentConfiguration = ButtonContentConfiguration(menu: menu)
} else if identifier == Self.SyncServerItem { } else if identifier == Self.SyncServerItem {
cell.contentConfiguration = TextFieldContentConfiguration( cell.contentConfiguration = TextFieldContentConfiguration(
@@ -173,17 +209,20 @@ class GeneralSettingsViewController: UIViewController
placeholderText: "https://sync.server.com", placeholderText: "https://sync.server.com",
textChanged: { newString in textChanged: { newString in
Settings.shared.syncServer = newString Settings.shared.syncServer = newString
} },
pressedReturn: { $0.resignFirstResponder() },
keyboardType: .URL,
returnKeyType: .done
) )
} }
#if !targetEnvironment(macCatalyst) #if !targetEnvironment(macCatalyst)
cell.backgroundConfiguration = UIBackgroundConfiguration.listGroupedCell() cell.backgroundConfiguration = UIBackgroundConfiguration.listCell()
#endif #endif
} }
let sectionHeaderRegistry = UICollectionView.SupplementaryRegistration<UICollectionViewCell>(elementKind: UICollectionView.elementKindSectionHeader, handler: { let sectionHeaderRegistry = UICollectionView.SupplementaryRegistration<UICollectionViewCell>(elementKind: UICollectionView.elementKindSectionHeader, handler: {
(cell, string, indexPath) in (cell, string, indexPath) in
let sectionName = Section.allCases[indexPath.section].rawValue let sectionName = Section.allCases[indexPath.section].rawValue
cell.contentConfiguration = Self.sectionHeaderConfiguration(forIdiom: Self.staticIdiom, sectionName: sectionName) cell.contentConfiguration = Self.sectionHeaderConfiguration(forIdiom: Self.staticIdiom, sectionName: sectionName)
}) })
@@ -193,7 +232,7 @@ class GeneralSettingsViewController: UIViewController
collectionView.backgroundColor = .systemGroupedBackground collectionView.backgroundColor = .systemGroupedBackground
dataSource = UICollectionViewDiffableDataSource<Section, Item>(collectionView: collectionView) { dataSource = UICollectionViewDiffableDataSource<Section, Item>(collectionView: collectionView) {
(collectionView, indexPath, identifier) in (collectionView, indexPath, identifier) in
return collectionView.dequeueConfiguredReusableCell(using: itemCellRegistry, for: indexPath, item: identifier) return collectionView.dequeueConfiguredReusableCell(using: itemCellRegistry, for: indexPath, item: identifier)
} }
@@ -201,10 +240,66 @@ class GeneralSettingsViewController: UIViewController
return collectionView.dequeueConfiguredReusableSupplementary(using: sectionHeaderRegistry, for: indexPath) return collectionView.dequeueConfiguredReusableSupplementary(using: sectionHeaderRegistry, for: indexPath)
} }
self.viewModel = viewModel
super.init(nibName: nil, bundle: nil) super.init(nibName: nil, bundle: nil)
collectionView.delegate = self
tabBarItem.title = "General" tabBarItem.title = "General"
tabBarItem.image = UIImage(systemName: "gear") tabBarItem.image = UIImage(systemName: "gear")
let resetPopup = { [weak self] in
guard let self else { return }
var snapshot = dataSource.snapshot()
snapshot.reloadItems([ Self.SearchProviderPopupItem ])
dataSource.apply(snapshot, animatingDifferences: false)
}
viewModel.onManageSearchEngines = { [weak self] in
guard let self else { return }
// Reset menu item (to show currently selected search engine)
resetPopup()
let viewController = ManageSearchEnginesViewController()
viewController.title = "Manage Search Engines"
viewController.model.onAddSearchEngine = { [weak self] in
guard let self else { return }
let addSearchEngineViewController = AddSearchEngineViewController()
let navController = UINavigationController(rootViewController: addSearchEngineViewController)
// Cancel
addSearchEngineViewController.navigationItem.leftBarButtonItem = UIBarButtonItem(systemItem: .cancel, primaryAction: UIAction { _ in
navController.dismiss(animated: true)
})
// Done
addSearchEngineViewController.navigationItem.rightBarButtonItem = UIBarButtonItem(systemItem: .done, primaryAction: UIAction { [weak self] _ in
self?.saveCustomSearchEngine(model: addSearchEngineViewController.viewModel)
navController.dismiss(animated: true)
resetPopup()
viewController.model.reloadConfiguredEngines()
})
self.present(navController, animated: true)
}
viewController.model.onChangeEngines = {
resetPopup()
}
#if targetEnvironment(macCatalyst)
let alertController = UIAlertController(title: "Not yet implemented for macOS.", message: nil, preferredStyle: .alert)
alertController.addAction(UIAlertAction(title: "OK", style: .default, handler: { _ in alertController.dismiss(animated: true) }))
present(alertController, animated: true)
#else
navigationController?.pushViewController(viewController, animated: true)
#endif
}
} }
required init?(coder: NSCoder) { required init?(coder: NSCoder) {
@@ -217,14 +312,54 @@ class GeneralSettingsViewController: UIViewController
override func viewDidLoad() { override func viewDidLoad() {
super.viewDidLoad() super.viewDidLoad()
applySnapshot(animatingDifferences: false)
var snapshot = dataSource.snapshot()
snapshot.appendSections(Section.allCases)
// iOS
// snapshot.appendItems(Settings.SearchProviderSetting.allCases.map { $0.rawValue }, toSection: .searchEngine)
snapshot.appendItems([ Self.SearchProviderPopupItem ], toSection: .searchEngine)
snapshot.appendItems([ Self.SyncServerItem ], toSection: .syncServer)
dataSource.apply(snapshot, animatingDifferences: false)
} }
// MARK: - Types
@MainActor
@Observable
class ViewModel
{
var onManageSearchEngines: () -> Void = {}
}
}
extension GeneralSettingsViewController : UICollectionViewDelegate {
func collectionView(_ collectionView: UICollectionView, shouldSelectItemAt indexPath: IndexPath) -> Bool {
false
}
}
private extension GeneralSettingsViewController {
func applySnapshot(animatingDifferences: Bool) {
var snapshot = NSDiffableDataSourceSnapshot<Section, Item>()
snapshot.appendSections(Section.allCases)
snapshot.appendItems([ Self.SearchProviderPopupItem ], toSection: .searchEngine)
snapshot.appendItems([ Self.SyncServerItem ], toSection: .syncServer)
dataSource.apply(snapshot, animatingDifferences: animatingDifferences)
}
func saveCustomSearchEngine(model: AddSearchEngineView.ViewModel) {
let name = model.name.trimmingCharacters(in: .whitespacesAndNewlines)
let url = model.url.trimmingCharacters(in: .whitespacesAndNewlines)
guard !name.isEmpty, !url.isEmpty else { return }
// Require placeholder
guard url.contains("%q") || url.contains("%s") else { return }
// Save
var engines = Settings.shared.searchEngines
engines[name] = url
Settings.shared.searchEngines = engines
if model.makeDefault {
Settings.shared.defaultSearchEngineName = name
}
// Reset inputs and refresh UI
var snapshot = dataSource.snapshot()
snapshot.reloadItems([ Self.SearchProviderPopupItem ])
dataSource.apply(snapshot, animatingDifferences: false)
}
} }
@@ -0,0 +1,139 @@
//
// ManageSearchEnginesViewController.swift
// App
//
// Created by James Magahern on 9/29/25.
//
import SwiftUI
import UIKit
@MainActor
struct ManageSearchEnginesView: View
{
@State var model: ViewModel
var body: some View {
Form {
Section {
List($model.configuredEngines, editActions: .delete) { row in
HStack {
Image(uiImage: .checkmark)
.opacity(row.wrappedValue.isDefault ? 1.0 : 0.0)
VStack(alignment: .leading) {
Text(row.wrappedValue.name)
.font(.body)
.bold()
Text(row.wrappedValue.url)
.font(.caption)
}
}
.id(row.id)
.swipeActions(edge: .leading) {
Button { model.makeDefault(row.id) } label: {
Label("Make Default", systemImage: "checkmark")
}
.tint(.blue)
}
.swipeActions(edge: .trailing) {
Button(role: .destructive) { model.deleteEngine(row.id) } label: {
Label("Delete", systemImage: "trash")
}
.disabled(model.configuredEngines.count <= 1)
}
}
}
Section {
Button("Add Search Engine…") {
model.onAddSearchEngine()
}
}
}
}
// MARK: - Types
@MainActor
@Observable
class ViewModel
{
var configuredEngines: [ConfiguredEngine] = []
var onAddSearchEngine: () -> Void = {}
var onChangeEngines: () -> Void = {}
init() {
reloadConfiguredEngines()
}
func reloadConfiguredEngines() {
let defaultEngine = Settings.shared.defaultSearchEngineName
self.configuredEngines = Settings.shared.searchEngines.map { val in
ConfiguredEngine(
name: val.key,
url: val.value,
isDefault: val.key == defaultEngine
)
}
}
func deleteEngine(_ id: ConfiguredEngine.ID) {
let engine = configuredEngines.first { $0.url == id }!
var engines = Settings.shared.searchEngines
engines.removeValue(forKey: engine.name)
Settings.shared.searchEngines = engines
if engine.isDefault {
// Pick another default
if let firstEngine = engines.first {
Settings.shared.defaultSearchEngineName = firstEngine.key
}
}
reloadConfiguredEngines()
onChangeEngines()
}
func makeDefault(_ id: ConfiguredEngine.ID) {
let engineName = configuredEngines.first { $0.url == id }!.name
Settings.shared.defaultSearchEngineName = engineName
reloadConfiguredEngines()
onChangeEngines()
}
}
struct ConfiguredEngine: Identifiable
{
let name: String
let url: String
let isDefault: Bool
var id: String { url }
}
}
@MainActor
class ManageSearchEnginesViewController: UIHostingController<ManageSearchEnginesView>
{
let model: ManageSearchEnginesView.ViewModel
init() {
self.model = ManageSearchEnginesView.ViewModel()
super.init(rootView: ManageSearchEnginesView(model: model))
}
required dynamic init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
#Preview {
@Previewable @State var model = ManageSearchEnginesView.ViewModel()
ManageSearchEnginesView(model: model)
}
@@ -229,13 +229,19 @@ class CreateRedirectRuleViewController: UICollectionViewController
sectionHeaderImageView.contentMode = .scaleAspectFit sectionHeaderImageView.contentMode = .scaleAspectFit
fromHostField.placeholder = "https://fromhostname.com" fromHostField.placeholder = "fromhostname.com"
fromHostField.autocorrectionType = .no fromHostField.autocorrectionType = .no
fromHostField.autocapitalizationType = .none fromHostField.autocapitalizationType = .none
fromHostField.keyboardType = .URL
fromHostField.returnKeyType = .next
fromHostField.delegate = self
toHostField.placeholder = "https://tohostname.com" toHostField.placeholder = "tohostname.com"
toHostField.autocorrectionType = .no toHostField.autocorrectionType = .no
toHostField.autocapitalizationType = .none toHostField.autocapitalizationType = .none
toHostField.keyboardType = .URL
toHostField.returnKeyType = .done
toHostField.delegate = self
} }
required init?(coder: NSCoder) { required init?(coder: NSCoder) {
@@ -256,3 +262,15 @@ class CreateRedirectRuleViewController: UICollectionViewController
} }
} }
extension CreateRedirectRuleViewController : UITextFieldDelegate {
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
if textField == fromHostField {
toHostField.becomeFirstResponder()
} else if textField == toHostField {
toHostField.resignFirstResponder()
}
return false
}
}
+17 -15
View File
@@ -75,25 +75,27 @@ class Settings
{ {
static let shared = Settings() static let shared = Settings()
public enum SearchProviderSetting: String, CaseIterable { // Map of search engine name -> URL template containing %q or %s placeholder
case google = "Google" // Defaults preserve the previous built-in engines
case duckduckgo = "DuckDuckGo" @SettingProperty(key: "searchEngines")
case searxnor = "Searx.nor" public var searchEngines: [String: String] = [
case whoogle = "Whoogle.nor" "Google": "https://google.com/search?q=%q&gbv=1",
"DuckDuckGo": "https://html.duckduckgo.com/html/?q=%q",
]
func provider() -> SearchProvider { // Name of the default search engine from `searchEngines`
switch self { @SettingProperty(key: "defaultSearchEngine")
case .google: return SearchProvider.google public var defaultSearchEngineName: String = "Searx.nor"
case .duckduckgo: return SearchProvider.duckduckgo
case .searxnor: return SearchProvider.searxnor // Convenience to build a SearchProvider from current default
case .whoogle: return SearchProvider.whoogle func currentSearchProvider() -> SearchProvider {
} if let template = searchEngines[defaultSearchEngineName] {
return SearchProvider.fromTemplate(template)
} }
// Fallback to Google if something goes wrong
return SearchProvider.fromTemplate("https://google.com/search?q=%q&gbv=1")
} }
@SettingProperty(key: "searchProvider")
public var searchProvider: SearchProviderSetting = .searxnor
@SettingProperty(key: "redirectRules") @SettingProperty(key: "redirectRules")
public var redirectRules: [String: String] = [:] public var redirectRules: [String: String] = [:]
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

@@ -1,110 +1,114 @@
{ {
"images" : [ "images" : [
{ {
"filename" : "icon-Notification@2x.png", "filename" : "AppIcon-20.0x20.0@2x.png",
"idiom" : "iphone", "idiom" : "universal",
"platform" : "ios",
"scale" : "2x", "scale" : "2x",
"size" : "20x20" "size" : "20x20"
}, },
{ {
"filename" : "icon-Notification@3x.png", "filename" : "AppIcon-20.0x20.0@3x.png",
"idiom" : "iphone", "idiom" : "universal",
"platform" : "ios",
"scale" : "3x", "scale" : "3x",
"size" : "20x20" "size" : "20x20"
}, },
{ {
"filename" : "icon-Settings@2x.png", "filename" : "AppIcon-29.0x29.0@2x.png",
"idiom" : "iphone", "idiom" : "universal",
"platform" : "ios",
"scale" : "2x", "scale" : "2x",
"size" : "29x29" "size" : "29x29"
}, },
{ {
"filename" : "icon-Settings@3x.png", "filename" : "AppIcon-29.0x29.0@3x.png",
"idiom" : "iphone", "idiom" : "universal",
"platform" : "ios",
"scale" : "3x", "scale" : "3x",
"size" : "29x29" "size" : "29x29"
}, },
{ {
"filename" : "icon-Spotlight-40@2x.png", "filename" : "AppIcon-38.0x38.0@2x.png",
"idiom" : "iphone", "idiom" : "universal",
"platform" : "ios",
"scale" : "2x",
"size" : "38x38"
},
{
"filename" : "AppIcon-38.0x38.0@3x.png",
"idiom" : "universal",
"platform" : "ios",
"scale" : "3x",
"size" : "38x38"
},
{
"filename" : "AppIcon-40.0x40.0@2x.png",
"idiom" : "universal",
"platform" : "ios",
"scale" : "2x", "scale" : "2x",
"size" : "40x40" "size" : "40x40"
}, },
{ {
"filename" : "icon-Spotlight-40@3x.png", "filename" : "AppIcon-40.0x40.0@3x.png",
"idiom" : "iphone", "idiom" : "universal",
"platform" : "ios",
"scale" : "3x", "scale" : "3x",
"size" : "40x40" "size" : "40x40"
}, },
{ {
"filename" : "icon-60@2x.png", "filename" : "AppIcon-60.0x60.0@2x.png",
"idiom" : "iphone", "idiom" : "universal",
"platform" : "ios",
"scale" : "2x", "scale" : "2x",
"size" : "60x60" "size" : "60x60"
}, },
{ {
"filename" : "icon-60@3x.png", "filename" : "AppIcon-60.0x60.0@3x.png",
"idiom" : "iphone", "idiom" : "universal",
"platform" : "ios",
"scale" : "3x", "scale" : "3x",
"size" : "60x60" "size" : "60x60"
}, },
{ {
"filename" : "icon-Notification~ipad.png", "filename" : "AppIcon-64.0x64.0@2x.png",
"idiom" : "ipad", "idiom" : "universal",
"scale" : "1x", "platform" : "ios",
"size" : "20x20"
},
{
"filename" : "icon-Notification@2x ~ipad.png",
"idiom" : "ipad",
"scale" : "2x", "scale" : "2x",
"size" : "20x20" "size" : "64x64"
}, },
{ {
"filename" : "icon-Settings~ipad.png", "filename" : "AppIcon-64.0x64.0@3x.png",
"idiom" : "ipad", "idiom" : "universal",
"scale" : "1x", "platform" : "ios",
"size" : "29x29" "scale" : "3x",
"size" : "64x64"
}, },
{ {
"filename" : "icon-Settings@2x~ipad.png", "filename" : "AppIcon-68.0x68.0@2x.png",
"idiom" : "ipad", "idiom" : "universal",
"platform" : "ios",
"scale" : "2x", "scale" : "2x",
"size" : "29x29" "size" : "68x68"
}, },
{ {
"filename" : "icon-Spotlight-40~ipad.png", "filename" : "AppIcon-76.0x76.0@2x.png",
"idiom" : "ipad", "idiom" : "universal",
"scale" : "1x", "platform" : "ios",
"size" : "40x40"
},
{
"filename" : "icon-Spotlight-40@2x~ipad.png",
"idiom" : "ipad",
"scale" : "2x",
"size" : "40x40"
},
{
"filename" : "icon-76~ipad.png",
"idiom" : "ipad",
"scale" : "1x",
"size" : "76x76"
},
{
"filename" : "icon-76@2x~ipad.png",
"idiom" : "ipad",
"scale" : "2x", "scale" : "2x",
"size" : "76x76" "size" : "76x76"
}, },
{ {
"filename" : "icon-83.5@2x~ipad.png", "filename" : "AppIcon-83.5x83.5@2x.png",
"idiom" : "ipad", "idiom" : "universal",
"platform" : "ios",
"scale" : "2x", "scale" : "2x",
"size" : "83.5x83.5" "size" : "83.5x83.5"
}, },
{ {
"filename" : "iconios~marketing-1.png", "filename" : "AppIcon-1024.0x1024.0@1x.png",
"idiom" : "ios-marketing", "idiom" : "universal",
"platform" : "ios",
"scale" : "1x", "scale" : "1x",
"size" : "1024x1024" "size" : "1024x1024"
} }
Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 762 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 336 KiB

+4
View File
@@ -34,6 +34,10 @@
</array> </array>
<key>CFBundleVersion</key> <key>CFBundleVersion</key>
<string>$(CURRENT_PROJECT_VERSION)</string> <string>$(CURRENT_PROJECT_VERSION)</string>
<key>ITSAppUsesNonExemptEncryption</key>
<false/>
<key>LSApplicationCategoryType</key>
<string>public.app-category.utilities</string>
<key>LSRequiresIPhoneOS</key> <key>LSRequiresIPhoneOS</key>
<true/> <true/>
<key>NSAppTransportSecurity</key> <key>NSAppTransportSecurity</key>
@@ -5,8 +5,9 @@
#import "SBRProcessBundleBridge.h" #import "SBRProcessBundleBridge.h"
#import "SBRScriptPolicy.h" #import "SBRScriptPolicy.h"
#import "WebKitDefines.h"
// SPI // SPI
#import <UIKit/UITextField_Private.h>
#import <WebKit/WKWebViewPrivate.h> #import <WebKit/WKWebViewPrivate.h>
#import <WebKit/_WKFindDelegate.h> #import <WebKit/_WKFindDelegate.h>
+2 -4
View File
@@ -2,13 +2,11 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0"> <plist version="1.0">
<dict> <dict>
<key>com.apple.developer.web-browser</key>
<true/>
<key>com.apple.security.app-sandbox</key> <key>com.apple.security.app-sandbox</key>
<true/> <true/>
<key>com.apple.security.network.client</key> <key>com.apple.security.network.client</key>
<true/> <true/>
<key>com.apple.developer.web-browser</key>
<true/>
<key>com.apple.developer.device-information.user-assigned-device-name</key>
<true/>
</dict> </dict>
</plist> </plist>
+19
View File
@@ -0,0 +1,19 @@
//
// WebKitDefines.h
// SBrowser
//
// Created by James Magahern on 4/11/25.
//
#ifndef WebKitDefines_h
#define WebKitDefines_h
#define WK_API_AVAILABLE(...)
#define WK_API_UNAVAILABLE(...)
#define WK_CLASS_AVAILABLE(...) __attribute__((visibility("default"))) WK_API_AVAILABLE(__VA_ARGS__)
#define WK_API_DEPRECATED(_message, ...) __attribute__((deprecated(_message)))
#define WK_API_DEPRECATED_WITH_REPLACEMENT(_replacement, ...) __attribute__((deprecated("use " #_replacement)))
#define WK_CLASS_DEPRECATED_WITH_REPLACEMENT(_replacement, ...) __attribute__((visibility("default"))) __attribute__((deprecated("use " #_replacement)))
#endif /* WebKitDefines_h */
+13 -13
View File
@@ -19,12 +19,7 @@ class Tab: NSObject, SBRProcessBundleBridgeDelegate
public var tabInfo: TabInfo { public var tabInfo: TabInfo {
get { get {
TabInfo( return _tabInfo
title: loadedWebView?.title,
urlString: loadedWebView?.url?.absoluteString ?? self.homeURL?.absoluteString,
faviconData: self.favicon?.pngData(),
identifier: self.identifier
)
} }
} }
@@ -43,16 +38,14 @@ class Tab: NSObject, SBRProcessBundleBridgeDelegate
} }
public var policyManager: ResourcePolicyManager public var policyManager: ResourcePolicyManager
// Persisted snapshot of visible tab metadata; do not recompute on read.
var _tabInfo: TabInfo = TabInfo()
private var loadedWebView: WKWebView? = nil private var loadedWebView: WKWebView? = nil
public var title: String? { get { tabInfo.title } } public var title: String? { get { tabInfo.title } }
public var url: URL? { public var url: URL? {
get { if let urlString = tabInfo.urlString { return URL(string: urlString) }
if let urlString = tabInfo.urlString { return nil
return URL(string: urlString)
}
return nil
}
} }
public var javaScriptEnabled: Bool = false { public var javaScriptEnabled: Bool = false {
@@ -82,6 +75,7 @@ class Tab: NSObject, SBRProcessBundleBridgeDelegate
@Published public var favicon: UIImage? @Published public var favicon: UIImage?
public var loadError: Error? public var loadError: Error?
public var contentProcessTerminated: Bool = false
private var faviconHost: String? private var faviconHost: String?
private var faviconRequest: AnyCancellable? private var faviconRequest: AnyCancellable?
@@ -111,6 +105,10 @@ class Tab: NSObject, SBRProcessBundleBridgeDelegate
super.init() super.init()
bridge.delegate = self bridge.delegate = self
// Initialize snapshot metadata
_tabInfo.identifier = self.identifier
if let url { _tabInfo.urlString = url.absoluteString }
} }
deinit { deinit {
@@ -118,6 +116,8 @@ class Tab: NSObject, SBRProcessBundleBridgeDelegate
} }
func beginLoadingURL(_ url: URL) { func beginLoadingURL(_ url: URL) {
// Update snapshot immediately so UI keeps URL even if process jettisons.
_tabInfo.urlString = url.absoluteString
let request = URLRequest(url: url) let request = URLRequest(url: url)
webView.load(request) webView.load(request)
} }
+19 -3
View File
@@ -33,6 +33,10 @@ class TabController
tabs.first { $0.identifier == identifier } tabs.first { $0.identifier == identifier }
} }
func tab(forWebView webView: WKWebView) -> Tab? {
tabs.first { $0.webView == webView }
}
func createNewTab(url: URL?) -> Tab { func createNewTab(url: URL?) -> Tab {
return self.createNewTab(url: url, webViewConfiguration: nil) return self.createNewTab(url: url, webViewConfiguration: nil)
} }
@@ -45,16 +49,28 @@ class TabController
tabs.append(tab) tabs.append(tab)
} }
// Title observation // Title observation: update snapshot and notify delegate.
tab.titleObservation = tab.webView.observe(\.title, changeHandler: { [weak tab, weak self] (webView, change) in tab.titleObservation = tab.webView.observe(\.title, changeHandler: { [weak tab, weak self] (webView, change) in
if let tab = tab, let self = self, let delegate = self.controllerDelegate { guard let tab = tab else { return }
if let newTitle = webView.title, !newTitle.isEmpty {
tab._tabInfo.title = newTitle
}
if let self = self, let delegate = self.controllerDelegate {
delegate.tabController(self, didUpdateTitle: webView.title ?? "", forTab: tab) delegate.tabController(self, didUpdateTitle: webView.title ?? "", forTab: tab)
} }
}) })
// URL observation: persist the latest URL in the snapshot.
tab.urlObservation = tab.webView.observe(\.url, changeHandler: { [weak tab] (webView, change) in
guard let tab = tab else { return }
tab._tabInfo.urlString = webView.url?.absoluteString ?? tab._tabInfo.urlString
})
// Favicon Observation // Favicon Observation
tab.faviconObservation = tab.$favicon.receive(on: RunLoop.main).sink { [weak tab, weak self] val in tab.faviconObservation = tab.$favicon.receive(on: RunLoop.main).sink { [weak tab, weak self] val in
if let tab = tab, let self = self, let delegate = self.controllerDelegate { guard let tab = tab else { return }
tab._tabInfo.faviconData = val?.pngData()
if let self = self, let delegate = self.controllerDelegate {
delegate.tabController(self, didUpdateFavicon: val, forTab: tab) delegate.tabController(self, didUpdateFavicon: val, forTab: tab)
} }
} }
+6 -6
View File
@@ -6,7 +6,6 @@
// //
import UIKit import UIKit
import QuartzCore_Private
class SecurityIndicatorView: UIView class SecurityIndicatorView: UIView
{ {
@@ -62,10 +61,6 @@ class TitlebarView: UIView
private let separatorView = UIView(frame: .zero) private let separatorView = UIView(frame: .zero)
private let securityIndicatorView = SecurityIndicatorView() private let securityIndicatorView = SecurityIndicatorView()
override var _sceneDraggingBehaviorOnPan: _UIViewSceneDraggingBehaviorOnPan {
_UIViewSceneDraggingBehaviorOnPan.dragScene
}
convenience init() { convenience init() {
self.init(frame: .zero) self.init(frame: .zero)
addSubview(backgroundView) addSubview(backgroundView)
@@ -74,7 +69,7 @@ class TitlebarView: UIView
addSubview(separatorView) addSubview(separatorView)
separatorView.backgroundColor = UIColor(white: 1.0, alpha: 0.20) separatorView.backgroundColor = UIColor(white: 1.0, alpha: 0.20)
separatorView.layer.compositingFilter = kCAFilterPlusL separatorView.layer.compositingFilter = "kCAFilterPlusL"
titleLabelView.textColor = .white titleLabelView.textColor = .white
titleLabelView.layer.shadowColor = UIColor.black.cgColor titleLabelView.layer.shadowColor = UIColor.black.cgColor
@@ -88,6 +83,11 @@ class TitlebarView: UIView
securityIndicatorView.label.font = UIFont.systemFont(ofSize: 10.0) securityIndicatorView.label.font = UIFont.systemFont(ofSize: 10.0)
backgroundView.alpha = 0.98 backgroundView.alpha = 0.98
// Make draggable
if #available(iOS 17.0, *) {
addInteraction(UIWindowSceneDragInteraction())
}
} }
func setTitle(_ title: String) { func setTitle(_ title: String) {
+15 -2
View File
@@ -9,9 +9,18 @@ import UIKit
class ToolbarView: UIView class ToolbarView: UIView
{ {
var urlBar: URLBar? { didSet { containerView.addSubview(urlBar!) } } var urlBar: URLBar? {
didSet {
guard let urlBar else { return }
containerView.addSubview(urlBar)
var cancelButtonVisible: Bool = false { didSet { layoutSubviews() } } urlBar.textField.addAction(.init(handler: { [unowned self] _ in
layoutLatch.activate()
}), for: [ .editingDidBegin, .editingDidEnd ])
}
}
var cancelButtonVisible: Bool = false { didSet { setNeedsLayout() } }
let containerView = UIView(frame: .zero) let containerView = UIView(frame: .zero)
let backgroundView = GradientView(direction: .vertical, colors: [ let backgroundView = GradientView(direction: .vertical, colors: [
@@ -23,6 +32,9 @@ class ToolbarView: UIView
let leadingButtonsView = ToolbarButtonContainerView(frame: .zero) let leadingButtonsView = ToolbarButtonContainerView(frame: .zero)
let trailingButtonsView = ToolbarButtonContainerView(frame: .zero) let trailingButtonsView = ToolbarButtonContainerView(frame: .zero)
// Something I'm sure I'll regret: to ensure animation with the keyboard, latch layout until we get the right signal.
lazy var layoutLatch = LayoutLatch(self)
convenience init() convenience init()
{ {
self.init(frame: .zero) self.init(frame: .zero)
@@ -54,6 +66,7 @@ class ToolbarView: UIView
override func layoutSubviews() override func layoutSubviews()
{ {
guard !layoutLatch.latched else { return }
super.layoutSubviews() super.layoutSubviews()
let shadowPath = UIBezierPath(rect: bounds) let shadowPath = UIBezierPath(rect: bounds)
@@ -66,10 +66,13 @@ class ToolbarViewController: UIViewController
newTabButton.setImage(UIImage(systemName: "plus"), for: .normal) newTabButton.setImage(UIImage(systemName: "plus"), for: .normal)
let toolbarAnimationDuration: TimeInterval = 0.3 let toolbarAnimationDuration: TimeInterval = 0.3
urlBar.textField.addAction(.init(handler: { [traitCollection, toolbarView, urlBar] _ in urlBar.textField.addAction(.init(handler: { [weak self] _ in
if traitCollection.horizontalSizeClass == .compact { guard let self, let parent else { return }
UIView.animate(withDuration: toolbarAnimationDuration) {
toolbarView.cancelButtonVisible = urlBar.textField.isFirstResponder // xxx: Not sure why I need to refer to parent here. This view controller isn't inheriting anything.
if parent.traitCollection.horizontalSizeClass == .compact {
UIView.animate(withDuration: toolbarAnimationDuration) { [unowned self] in
self.toolbarView.cancelButtonVisible = self.urlBar.textField.isFirstResponder
} }
} }
}), for: [ .editingDidBegin, .editingDidEnd ]) }), for: [ .editingDidBegin, .editingDidEnd ])
@@ -78,12 +81,10 @@ class ToolbarViewController: UIViewController
urlBar.textField.resignFirstResponder() urlBar.textField.resignFirstResponder()
}), for: .touchUpInside) }), for: .touchUpInside)
traitCollectionDidChange(nil) reconfigureButtons(for: traitCollection.horizontalSizeClass)
} }
override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) { public func reconfigureButtons(for horizontalSizeClass: UIUserInterfaceSizeClass) {
super.traitCollectionDidChange(previousTraitCollection)
toolbarView.leadingButtonsView.removeAllButtonViews() toolbarView.leadingButtonsView.removeAllButtonViews()
toolbarView.trailingButtonsView.removeAllButtonViews() toolbarView.trailingButtonsView.removeAllButtonViews()
@@ -92,7 +93,7 @@ class ToolbarViewController: UIViewController
} }
// Setup toolbar based on trait collection // Setup toolbar based on trait collection
if traitCollection.horizontalSizeClass == .compact { if horizontalSizeClass == .compact {
toolbarView.trailingButtonsView.addButtonView(scriptControllerIconView) toolbarView.trailingButtonsView.addButtonView(scriptControllerIconView)
toolbarView.trailingButtonsView.addButtonView(newTabButton) toolbarView.trailingButtonsView.addButtonView(newTabButton)
toolbarView.trailingButtonsView.addButtonView(windowButton) toolbarView.trailingButtonsView.addButtonView(windowButton)
+20 -12
View File
@@ -153,20 +153,12 @@ class URLBar: ReliefButton
textField.font = .systemFont(ofSize: 13.0) textField.font = .systemFont(ofSize: 13.0)
textField.clearButtonMode = .never textField.clearButtonMode = .never
textField.placeholder = "URL or search term" textField.placeholder = "URL or search term"
textField.addAction(UIAction(handler: { [unowned self] _ in
// Mask view visibility is affected by editing state.
self.layoutSubviews()
}), for: [ .editingDidBegin, .editingDidEnd ])
textField.keyCommands = [ textField.keyCommands = [
UIKeyCommand(action: #selector(Self.downKeyPressed), input: UIKeyCommand.inputDownArrow) UIKeyCommand(action: #selector(Self.downKeyPressed), input: UIKeyCommand.inputDownArrow)
.prioritizeOverSystem() .prioritizeOverSystem()
] ]
addSubview(textField) addSubview(textField)
textField.addAction(.init(handler: { [textField, refreshButton ] _ in
refreshButton.isHidden = textField.isFirstResponder
}), for: [ .editingDidBegin, .editingDidEnd ])
refreshButton.tintColor = .secondaryLabel refreshButton.tintColor = .secondaryLabel
refreshButton.setImage(refreshImage, for: .normal) refreshButton.setImage(refreshImage, for: .normal)
refreshButton.isPointerInteractionEnabled = true refreshButton.isPointerInteractionEnabled = true
@@ -189,8 +181,9 @@ class URLBar: ReliefButton
documentSeparatorView.backgroundColor = .secondarySystemFill documentSeparatorView.backgroundColor = .secondarySystemFill
addSubview(documentSeparatorView) addSubview(documentSeparatorView)
controlsView.autoresizingMask = []
controlsView.clearButton.addAction(.init(handler: { [textField] _ in controlsView.clearButton.addAction(.init(handler: { [textField] _ in
textField.clearText() textField.text = ""
}), for: .primaryActionTriggered) }), for: .primaryActionTriggered)
controlsView.autocorrectButton.addAction(.init(handler: { [unowned self] _ in controlsView.autocorrectButton.addAction(.init(handler: { [unowned self] _ in
@@ -281,8 +274,9 @@ class URLBar: ReliefButton
} }
private func fadeBackgroundImageForSize(_ size: CGSize, cutoffLocation: CGFloat) -> UIImage? { private func fadeBackgroundImageForSize(_ size: CGSize, cutoffLocation: CGFloat) -> UIImage? {
var image: UIImage? = nil guard size.width > .leastNonzeroMagnitude && size.height > .leastNonzeroMagnitude else { return nil }
var image: UIImage? = nil
UIGraphicsBeginImageContext(CGSize(width: size.width, height: 1.0)) UIGraphicsBeginImageContext(CGSize(width: size.width, height: 1.0))
if let context = UIGraphicsGetCurrentContext() { if let context = UIGraphicsGetCurrentContext() {
let gradientColorsArray = [ let gradientColorsArray = [
@@ -292,6 +286,7 @@ class URLBar: ReliefButton
UIColor(white: 1.0, alpha: 0.08).cgColor UIColor(white: 1.0, alpha: 0.08).cgColor
] ]
let cutoffLocation = CGFloat.minimum(0.9, cutoffLocation)
let locations: [CGFloat] = [ let locations: [CGFloat] = [
0.0, cutoffLocation, cutoffLocation + 0.10, 1.0 0.0, cutoffLocation, cutoffLocation + 0.10, 1.0
] ]
@@ -343,6 +338,9 @@ class URLBar: ReliefButton
) )
documentSeparatorView.frame = documentSeparatorView.frame.insetBy(dx: 0.0, dy: 3.0) documentSeparatorView.frame = documentSeparatorView.frame.insetBy(dx: 0.0, dy: 3.0)
// Text field controls
controlsView.frame = CGRect(origin: controlsView.frame.origin, size: controlsView.sizeThatFits(bounds.size))
// Text field // Text field
let textFieldPadding: CGFloat = 6.0 let textFieldPadding: CGFloat = 6.0
let textFieldOrigin = CGPoint(x: documentButton.frame.maxX + textFieldPadding, y: 0.0) let textFieldOrigin = CGPoint(x: documentButton.frame.maxX + textFieldPadding, y: 0.0)
@@ -360,8 +358,18 @@ class URLBar: ReliefButton
let refreshButtonSize = CGSize(width: textField.frame.height, height: textField.frame.height) let refreshButtonSize = CGSize(width: textField.frame.height, height: textField.frame.height)
refreshButton.frame = CGRect(origin: CGPoint(x: bounds.width - refreshButtonSize.width, y: 0), size: refreshButtonSize) refreshButton.frame = CGRect(origin: CGPoint(x: bounds.width - refreshButtonSize.width, y: 0), size: refreshButtonSize)
// Refresh vs. controls view visibility
let isFocused = textField.isFirstResponder
if isFocused {
controlsView.alpha = 1.0
refreshButton.alpha = 0.0
} else {
controlsView.alpha = 0.0
refreshButton.alpha = 1.0
}
// Error button // Error button
if case .error(error: _) = loadProgress, !textField.isFirstResponder { if case .error(error: _) = loadProgress, !isFocused {
errorButton.isHidden = false errorButton.isHidden = false
errorButton.sizeToFit() errorButton.sizeToFit()
errorButton.frame = CGRect( errorButton.frame = CGRect(
@@ -379,7 +387,7 @@ class URLBar: ReliefButton
// Fade mask // Fade mask
fadeMaskView.frame = textField.bounds fadeMaskView.frame = textField.bounds
fadeMaskView.image = fadeBackgroundImageForSize(fadeMaskView.frame.size, cutoffLocation: fadeCutoffLocation) fadeMaskView.image = fadeBackgroundImageForSize(fadeMaskView.frame.size, cutoffLocation: fadeCutoffLocation)
if !textField.isFirstResponder { if !isFocused {
textField.mask = fadeMaskView textField.mask = fadeMaskView
} else { } else {
textField.mask = nil textField.mask = nil
+2 -2
View File
@@ -33,14 +33,14 @@ extension CGRect
public func centeredY(inRect: CGRect) -> CGRect { public func centeredY(inRect: CGRect) -> CGRect {
var rect = self var rect = self
rect.origin.y = CGRound(inRect.origin.y + (inRect.height - rect.height) / 2.0) rect.origin.y = (inRect.origin.y + (inRect.height - rect.height) / 2.0).rounded()
return rect return rect
} }
public func centeredX(inRect: CGRect) -> CGRect { public func centeredX(inRect: CGRect) -> CGRect {
var rect = self var rect = self
rect.origin.x = CGRound(inRect.origin.x + (inRect.width - rect.width) / 2.0) rect.origin.x = (inRect.origin.x + (inRect.width - rect.width) / 2.0).rounded()
return rect return rect
} }
+30
View File
@@ -0,0 +1,30 @@
//
// LayoutLatch.swift
// App
//
// Created by James Magahern on 8/10/23.
//
import Foundation
class LayoutLatch {
public weak var view: UIView?
public private(set) var latched: Bool = false
init(_ view: UIView) {
self.view = view
}
public func activate() {
latched = true
DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(100)) { [weak self] in
self?.deactivate()
}
}
public func deactivate() {
latched = false
view?.setNeedsLayout()
}
}
+33
View File
@@ -0,0 +1,33 @@
//
// StringUtilities.swift
// App
//
// Created by James Magahern on 7/31/24.
//
import Foundation
extension String {
internal func middleTruncatedString(maximumLength: Int) -> String {
if maximumLength > self.count {
return self
}
// Compute replacement range
let pivot = self.count / 2
let truncationLength = self.count - maximumLength
let pivotIndex = self.index(startIndex, offsetBy: pivot)
let startIndex = self.index(pivotIndex, offsetBy: -truncationLength / 2)
let endIndex = self.index(pivotIndex, offsetBy: truncationLength / 2)
return replacingCharacters(in: startIndex..<endIndex, with: "")
}
}
extension URL {
internal var securityOrigin: String {
// TODO: This could be smarter
return self.host() ?? ""
}
}
+9
View File
@@ -7,6 +7,15 @@
import UIKit import UIKit
infix operator .. : AssignmentPrecedence
@discardableResult @inline(__always) @inlinable
public func .. <T: Any>(it: T, apply: (inout T) throws -> Void) rethrows -> T {
var it = it
try apply(&it)
return it
}
protocol Conf { } protocol Conf { }
extension Conf { extension Conf {
@@ -36,8 +36,6 @@ NS_SWIFT_NAME(ProcessBundleBridge)
@property (nonatomic, assign) BOOL allowAllScripts; // default is NO @property (nonatomic, assign) BOOL allowAllScripts; // default is NO
@property (nonatomic, assign) BOOL darkModeEnabled; @property (nonatomic, assign) BOOL darkModeEnabled;
@property (nonatomic, readonly) BOOL webContentProcessConnected;
- (instancetype)initWithWebViewConfiguration:(nullable WKWebViewConfiguration *)webViewConfiguration NS_DESIGNATED_INITIALIZER; - (instancetype)initWithWebViewConfiguration:(nullable WKWebViewConfiguration *)webViewConfiguration NS_DESIGNATED_INITIALIZER;
- (void)policyDataSourceDidChange; - (void)policyDataSourceDidChange;
@@ -7,20 +7,120 @@
#import "SBRProcessBundleBridge.h" #import "SBRProcessBundleBridge.h"
#import "SBRWebProcessDelegate.h" #import "SBRScriptPolicy.h"
#import "SBRWebProcessProxy.h" #import "Hacks.h"
#import <OSLog/OSLog.h>
#import <stdatomic.h>
#import <WebKit/_WKRemoteObjectInterface.h> #import <WebKit/_WKRemoteObjectInterface.h>
#import <WebKit/_WKRemoteObjectRegistry.h> #import <WebKit/_WKRemoteObjectRegistry.h>
#import <WebKit/_WKProcessPoolConfiguration.h> #import <WebKit/_WKProcessPoolConfiguration.h>
#import <WebKit/_WKUserStyleSheet.h>
#import <WebKit/WKProcessPoolPrivate.h> #import <WebKit/WKProcessPoolPrivate.h>
#import <WebKit/WKWebViewPrivate.h> #import <WebKit/WKWebViewPrivate.h>
#import <WebKit/WKWebViewConfigurationPrivate.h> #import <WebKit/WKWebViewConfigurationPrivate.h>
#import <WebKit/WKUserContentControllerPrivate.h> #import <WebKit/WKContentRuleListStore.h>
@interface SBRProcessBundleBridge () <SBRWebProcessDelegate> #define WKUserStyleSheet id
#define WKUserStyleSheetEncodedClassName "X1dLVXNlclN0eWxlU2hlZXQ="
NSArray<NSString *> *CommonCDNList(void) {
static dispatch_once_t onceToken;
static NSArray<NSString *> *commonCDNList = nil;
dispatch_once(&onceToken, ^{
commonCDNList = @[
@"cdn.jsdelivr.net",
@"fsdn.net",
@"cdnjs.com",
@"osscdn.com",
@"code.jquery.com",
@"bootstrapcdn.com"
];
});
return commonCDNList;
}
@interface StyleSheet : NSObject <NSCopying>
@property (nonatomic, readonly, copy) NSString *source;
@property (nonatomic, readonly, copy) NSURL *baseURL;
@property (nonatomic, readonly, getter=isForMainFrameOnly) BOOL forMainFrameOnly;
- (instancetype)initWithSource:(NSString *)source forMainFrameOnly:(BOOL)forMainFrameOnly;
@end
// Note: We no longer proxy http/https via WKURLSchemeHandler; we use
// WebKit content blocking rules instead.
@interface WKUserContentController (Private)
- (void)__addUserStyleSheet:(WKUserStyleSheet)userStyleSheet;
- (void)__removeUserStyleSheet:(WKUserStyleSheet)userStyleSheet;
- (void)__addUserScriptImmediately:(WKUserScript *)userScript;
@end
@implementation WKUserContentController (Private)
- (void)__addUserStyleSheet:(id)userStyleSheet
{
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
[self performSelector:DecodedSelector("X2FkZFVzZXJTdHlsZVNoZWV0Og==") withObject:userStyleSheet];
#pragma clang diagnostic pop
}
- (void)__removeUserStyleSheet:(id)userStyleSheet
{
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
[self performSelector:DecodedSelector("X3JlbW92ZVVzZXJTdHlsZVNoZWV0Og==") withObject:userStyleSheet];
#pragma clang diagnostic pop
}
- (void)__addUserScriptImmediately:(WKUserScript *)userScript
{
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
[self performSelector:DecodedSelector("X2FkZFVzZXJTY3JpcHRJbW1lZGlhdGVseTo=") withObject:userScript];
#pragma clang diagnostic pop
}
@end
#define LOG_DEBUG(format, ...) os_log_debug(_log, format, ##__VA_ARGS__)
#define LOG_ERROR(format, ...) os_log_error(_log, format, ##__VA_ARGS__)
static os_log_t _log;
__attribute__((constructor))
static void initialize_log(void) {
_log = os_log_create("net.buzzert.attractor.webview", "bridge");
}
@interface NSURLResponse (BridgeAdditions)
@property (nonatomic, readonly) BOOL isJavascriptResponse;
@end
@implementation NSURLResponse (BridgeAdditions)
- (BOOL)isJavascriptResponse
{
NSString *extension = [[self URL] pathExtension];
if ([[extension lowercaseString] isEqualToString:@"js"]) {
return YES;
}
NSString *MIMEType = [self MIMEType];
if ([[MIMEType lowercaseString] containsString:@"javascript"]) {
return YES;
}
return NO;
}
@end
@interface SBRProcessBundleBridge ()
@end @end
@@ -28,43 +128,27 @@
WKWebView *_webView; WKWebView *_webView;
WKWebViewConfiguration *_webViewConfiguration; WKWebViewConfiguration *_webViewConfiguration;
WKProcessPool *_processPool; WKProcessPool *_processPool;
id<SBRWebProcessProxy> _webProcessProxy;
_WKUserStyleSheet *_darkModeStyleSheet; WKUserStyleSheet _darkModeStyleSheet;
WKUserScript *_readabilityScript; WKUserScript *_readabilityScript;
NSArray<WKUserScript *> *_userScripts; NSArray<WKUserScript *> *_userScripts;
// These come from settings. // These come from settings.
_WKUserStyleSheet *_customizedUserStylesheet; WKUserStyleSheet _customizedUserStylesheet;
WKUserScript *_customizedUserScript; WKUserScript *_customizedUserScript;
// Content blocking
WKContentRuleList *_activeScriptRuleList;
void *_urlKVOContext;
} }
- (void)tearDown - (void)tearDown
{ {
[[_webView _remoteObjectRegistry] unregisterExportedObject:self interface:[self _webProcessDelegateInterface]]; if (_webView) {
} @try { [_webView removeObserver:self forKeyPath:@"URL" context:_urlKVOContext]; }
@catch (__unused NSException *ex) {}
- (_WKRemoteObjectInterface *)_webProcessDelegateInterface }
{
static dispatch_once_t onceToken;
static _WKRemoteObjectInterface *interface = nil;
dispatch_once(&onceToken, ^{
interface = [_WKRemoteObjectInterface remoteObjectInterfaceWithProtocol:@protocol(SBRWebProcessDelegate)];
});
return interface;
}
- (_WKRemoteObjectInterface *)_webProcessProxyInterface
{
static dispatch_once_t onceToken;
static _WKRemoteObjectInterface *interface = nil;
dispatch_once(&onceToken, ^{
interface = [_WKRemoteObjectInterface remoteObjectInterfaceWithProtocol:@protocol(SBRWebProcessProxy)];
});
return interface;
} }
- (instancetype)initWithWebViewConfiguration:(WKWebViewConfiguration *)webViewConfiguration - (instancetype)initWithWebViewConfiguration:(WKWebViewConfiguration *)webViewConfiguration
@@ -74,30 +158,13 @@
if (!webViewConfiguration) { if (!webViewConfiguration) {
webViewConfiguration = [[WKWebViewConfiguration alloc] init]; webViewConfiguration = [[WKWebViewConfiguration alloc] init];
// Inject bundle
_WKProcessPoolConfiguration *poolConfiguration = [[_WKProcessPoolConfiguration alloc] init];
NSURL *bundleURL = [[[NSBundle mainBundle] builtInPlugInsURL] URLByAppendingPathComponent:@"SBrowserProcessBundle.bundle"];
// Make sure it exists. Bail if otherwise.
NSBundle *pluginBundle = [NSBundle bundleWithURL:bundleURL];
NSAssert(pluginBundle != nil, @"Attix process bundle not found at %@", bundleURL.path);
[poolConfiguration setInjectedBundleURL:bundleURL];
// Set up process pool // Set up process pool
_processPool = [[WKProcessPool alloc] _initWithConfiguration:poolConfiguration]; _processPool = [[WKProcessPool alloc] init];
// Initialize allowed origins now
NSDictionary<NSString *, NSNumber *> *policies = [_policyDataSource scriptPolicyTypeByOrigin];
NSArray<NSString *> *allowedOrigins = [[_policyDataSource allowedOriginsForScriptResources] allObjects];
[_processPool _setObject:allowedOrigins forBundleParameter:SBRGetAllowedOriginsKey()];
[_processPool _setObject:@(_allowAllScripts) forBundleParameter:SBRGetAllScriptsAllowedKey()];
[_processPool _setObject:policies forBundleParameter:SBRGetPolicyTypeByOriginKey()];
webViewConfiguration.processPool = _processPool; webViewConfiguration.processPool = _processPool;
webViewConfiguration._waitsForPaintAfterViewDidMoveToWindow = NO; webViewConfiguration._waitsForPaintAfterViewDidMoveToWindow = NO;
webViewConfiguration._applePayEnabled = YES; webViewConfiguration._applePayEnabled = YES;
// No http/https interception — rely on content blocking rules instead.
} }
_webViewConfiguration = webViewConfiguration; _webViewConfiguration = webViewConfiguration;
@@ -113,17 +180,16 @@
// Instantiate web view // Instantiate web view
WKWebView *webView = [[WKWebView alloc] initWithFrame:CGRectZero configuration:webViewConfiguration]; WKWebView *webView = [[WKWebView alloc] initWithFrame:CGRectZero configuration:webViewConfiguration];
if ([webView respondsToSelector:@selector(_setFindInteractionEnabled:)]) { if (@available(iOS 16.0, *)) {
webView._findInteractionEnabled = YES; webView.findInteractionEnabled = YES;
} }
// Configure proxy interface (interface to remote web process)
_webProcessProxy = [[webView _remoteObjectRegistry] remoteObjectProxyWithInterface:[self _webProcessProxyInterface]];
// Configure delegate interface (registering us as the web process delegate for the remote process)
[[webView _remoteObjectRegistry] registerExportedObject:self interface:[self _webProcessDelegateInterface]];
_webView = webView; _webView = webView;
_urlKVOContext = &_urlKVOContext; // unique context pointer
[_webView addObserver:self forKeyPath:@"URL" options:(NSKeyValueObservingOptionNew) context:_urlKVOContext];
// Initialize content blocking rules for current host
[self rebuildContentBlockingRulesForCurrentHost];
} }
return self; return self;
@@ -151,9 +217,7 @@
- (void)reloadCustomizedUserScriptsAndStylesheets - (void)reloadCustomizedUserScriptsAndStylesheets
{ {
WKUserContentController *userContentController = [_webViewConfiguration userContentController]; WKUserContentController *userContentController = [_webViewConfiguration userContentController];
if (_customizedUserScript) { [userContentController removeAllUserScripts];
[userContentController _removeUserScript:_customizedUserScript];
}
NSString *scriptSource = [[NSUserDefaults standardUserDefaults] stringForKey:@"userScript"]; NSString *scriptSource = [[NSUserDefaults standardUserDefaults] stringForKey:@"userScript"];
if ([scriptSource length]) { if ([scriptSource length]) {
@@ -162,55 +226,44 @@
} }
if (_customizedUserStylesheet) { if (_customizedUserStylesheet) {
[userContentController _removeUserStyleSheet:_customizedUserStylesheet]; [userContentController __removeUserStyleSheet:_customizedUserStylesheet];
} }
NSString *stylesheetSource = [[NSUserDefaults standardUserDefaults] stringForKey:@"userStylesheet"]; NSString *stylesheetSource = [[NSUserDefaults standardUserDefaults] stringForKey:@"userStylesheet"];
if ([stylesheetSource length]) { if ([stylesheetSource length]) {
_customizedUserStylesheet = [[_WKUserStyleSheet alloc] initWithSource:stylesheetSource forMainFrameOnly:YES]; _customizedUserStylesheet = [[DecodedClass(WKUserStyleSheetEncodedClassName) alloc] initWithSource:stylesheetSource forMainFrameOnly:YES];
[userContentController _addUserStyleSheet:_customizedUserStylesheet]; [userContentController __addUserStyleSheet:_customizedUserStylesheet];
} }
} }
#pragma mark <SBRWebProcessDelegate> #pragma mark Former <SBRWebProcessDelegate> methods
- (void)webProcessDidConnect
{
NSLog(@"SBRProcessBundleBridge: did connect. Saying hello, syncing allowlist");
_webContentProcessConnected = YES;
[_webProcessProxy hello];
[self policyDataSourceDidChange];
}
- (void)webProcessDidAllowScriptWithOrigin:(NSString *)origin - (void)webProcessDidAllowScriptWithOrigin:(NSString *)origin
{ {
[[self delegate] webProcess:self didAllowScriptResourceFromOrigin:origin]; dispatch_async(dispatch_get_main_queue(), ^{
[[self delegate] webProcess:self didAllowScriptResourceFromOrigin:origin];
});
} }
- (void)webProcessDidBlockScriptWithOrigin:(NSString *)origin - (void)webProcessDidBlockScriptWithOrigin:(NSString *)origin
{ {
[[self delegate] webProcess:self didBlockScriptResourceFromOrigin:origin]; dispatch_async(dispatch_get_main_queue(), ^{
[[self delegate] webProcess:self didBlockScriptResourceFromOrigin:origin];
});
} }
#pragma mark Actions #pragma mark Actions
- (void)policyDataSourceDidChange - (void)policyDataSourceDidChange
{ {
NSArray<NSString *> *allowedOrigins = [[_policyDataSource allowedOriginsForScriptResources] allObjects]; // Rebuild content blocking rules when policy changes.
[_processPool _setObject:allowedOrigins forBundleParameter:SBRGetAllowedOriginsKey()]; [self rebuildContentBlockingRulesForCurrentHost];
[_webProcessProxy syncAllowedResourceOrigins:allowedOrigins];
NSDictionary<NSString *, NSNumber *> *policyTypes = [_policyDataSource scriptPolicyTypeByOrigin];
[_processPool _setObject:policyTypes forBundleParameter:SBRGetPolicyTypeByOriginKey()];
[_webProcessProxy syncPolicyTypes:policyTypes];
} }
- (void)setAllowAllScripts:(BOOL)allowAllScripts - (void)setAllowAllScripts:(BOOL)allowAllScripts
{ {
_allowAllScripts = allowAllScripts; _allowAllScripts = allowAllScripts;
[_processPool _setObject:@(_allowAllScripts) forBundleParameter:SBRGetAllScriptsAllowedKey()]; [self rebuildContentBlockingRulesForCurrentHost];
[_webProcessProxy setAllScriptsAllowed:allowAllScripts];
} }
- (void)setDarkModeEnabled:(BOOL)darkModeEnabled - (void)setDarkModeEnabled:(BOOL)darkModeEnabled
@@ -223,16 +276,15 @@
if (!_darkModeStyleSheet) { if (!_darkModeStyleSheet) {
NSURL *styleSheetURL = [[NSBundle mainBundle] URLForResource:@"darkmode" withExtension:@"css"]; NSURL *styleSheetURL = [[NSBundle mainBundle] URLForResource:@"darkmode" withExtension:@"css"];
NSString *styleSheetSource = [NSString stringWithContentsOfURL:styleSheetURL encoding:NSUTF8StringEncoding error:nil]; NSString *styleSheetSource = [NSString stringWithContentsOfURL:styleSheetURL encoding:NSUTF8StringEncoding error:nil];
_darkModeStyleSheet = [[_WKUserStyleSheet alloc] initWithSource:styleSheetSource forMainFrameOnly:NO]; _darkModeStyleSheet = [[DecodedClass(WKUserStyleSheetEncodedClassName) alloc] initWithSource:styleSheetSource forMainFrameOnly:NO];
} }
[userContentController _addUserStyleSheet:_darkModeStyleSheet]; [userContentController __addUserStyleSheet:_darkModeStyleSheet];
} else if (_darkModeStyleSheet) { } else if (_darkModeStyleSheet) {
[userContentController _removeUserStyleSheet:_darkModeStyleSheet]; [userContentController __removeUserStyleSheet:_darkModeStyleSheet];
} }
} }
- (void)parseDocumentForReaderMode:(void (^)(NSString * _Nonnull))completionBlock - (void)parseDocumentForReaderMode:(void (^)(NSString * _Nonnull))completionBlock
{ {
WKUserContentController *userContentController = [_webViewConfiguration userContentController]; WKUserContentController *userContentController = [_webViewConfiguration userContentController];
@@ -241,20 +293,136 @@
_readabilityScript = [self _loadScriptForResource:@"Readability" withExtension:@"js"]; _readabilityScript = [self _loadScriptForResource:@"Readability" withExtension:@"js"];
} }
[userContentController _addUserScriptImmediately:_readabilityScript]; [userContentController __addUserScriptImmediately:_readabilityScript];
NSString *script = @"" NSString *script = @""
"var documentClone = document.cloneNode(true);" "var documentClone = document.cloneNode(true);"
"var article = new Readability(documentClone).parse();" "var article = new Readability(documentClone).parse();"
"article.content"; "article.content";
os_log_t log = _log;
[_webView evaluateJavaScript:script completionHandler:^(NSString *result, NSError * _Nullable error) { [_webView evaluateJavaScript:script completionHandler:^(NSString *result, NSError * _Nullable error) {
if (error != nil) { if (error != nil) {
NSLog(@"Bridge: Readability error: %@", error.localizedDescription); os_log_error(log, "Bridge: Readability error: %@", error.localizedDescription);
} else { } else {
completionBlock(result); completionBlock(result);
} }
}]; }];
} }
// MARK: - Content Blocking Rules
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context
{
if (context == _urlKVOContext && [keyPath isEqualToString:@"URL"]) {
[self rebuildContentBlockingRulesForCurrentHost];
return;
}
[super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
}
- (void)rebuildContentBlockingRulesForCurrentHost
{
NSString *hostOrigin = _webView.URL.host;
if (!hostOrigin) {
// No page loaded yet; clear any existing rules
[self applyContentRuleListJSON:nil withName:nil];
return;
}
// Determine policy for this host
NSNumber *policyType = nil;
@synchronized (_policyDataSource) {
NSDictionary<NSString *, NSNumber *> *policyTypes = [_policyDataSource scriptPolicyTypeByOrigin];
policyType = [policyTypes objectForKey:hostOrigin] ?: @(0);
}
// Alpha=0 Bravo=1 Charlie=2 Delta=3 Echo=4
SBRScriptOriginPolicyType type = [policyType integerValue];
if (_allowAllScripts || type >= SBRScriptOriginPolicyTypeEcho) {
// Echo or shields down: no blocking
[self applyContentRuleListJSON:nil withName:nil];
return;
}
// Base trigger applies only to this page's domain
NSMutableArray *rules = [NSMutableArray array];
NSDictionary *baseScriptTrigger = @{
@"resource-type" : @[ @"script" ],
@"if-domain" : @[ hostOrigin ]
};
if (type <= SBRScriptOriginPolicyTypeBravo) {
// Alpha or Bravo: block all external script subresources (inline JS is controlled via preferences elsewhere)
[rules addObject:@{ @"trigger": baseScriptTrigger, @"action": @{ @"type": @"block" } }];
} else {
// Charlie/Delta: block third-party scripts
NSMutableDictionary *trigger = [baseScriptTrigger mutableCopy];
trigger[@"load-type"] = @[ @"third-party" ];
[rules addObject:@{ @"trigger": trigger, @"action": @{ @"type": @"block" } }];
if (type >= SBRScriptOriginPolicyTypeDelta) {
// Delta: add allowlist for common CDNs
for (NSString *cdn in CommonCDNList()) {
NSString *escaped = [NSRegularExpression escapedPatternForString:cdn];
NSString *pattern = [NSString stringWithFormat:@".*://([^.]*\\.)?%@/", escaped];
[rules addObject:@{
@"trigger": @{
@"url-filter": pattern,
@"if-domain": @[ hostOrigin ],
@"resource-type": @[ @"script" ]
},
@"action": @{ @"type": @"ignore-previous-rules" }
}];
}
// Heuristic: allow cdn.<anything-with-family-name>/* where family name is the second-level label
NSArray<NSString *> *components = [hostOrigin componentsSeparatedByString:@"."];
if (components.count > 1) {
NSString *family = components[components.count - 2];
NSString *escapedFamily = [NSRegularExpression escapedPatternForString:family];
NSString *familyPattern = [NSString stringWithFormat:@".*://cdn\\.[^/]*%@[^/]*/", escapedFamily];
[rules addObject:@{
@"trigger": @{
@"url-filter": familyPattern,
@"if-domain": @[ hostOrigin ],
@"resource-type": @[ @"script" ]
},
@"action": @{ @"type": @"ignore-previous-rules" }
}];
}
}
}
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:rules options:0 error:nil];
NSString *json = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
NSString *name = [NSString stringWithFormat:@"net.buzzert.attractor.rules.%@", hostOrigin];
[self applyContentRuleListJSON:json withName:name];
}
- (void)applyContentRuleListJSON:(NSString *)json withName:(NSString *)name
{
WKUserContentController *controller = [_webViewConfiguration userContentController];
if (_activeScriptRuleList) {
[controller removeContentRuleList:_activeScriptRuleList];
_activeScriptRuleList = nil;
}
if (!json || !name) { return; }
WKContentRuleListStore *store = [WKContentRuleListStore defaultStore];
[store compileContentRuleListForIdentifier:name encodedContentRuleList:json completionHandler:^(WKContentRuleList * _Nullable ruleList, NSError * _Nullable error) {
if (error) {
LOG_ERROR("Failed to compile content rule list: %@", error.localizedDescription);
return;
}
dispatch_async(dispatch_get_main_queue(), ^{
self->_activeScriptRuleList = ruleList;
[controller addContentRuleList:ruleList];
LOG_DEBUG("Applied content rule list: %@", name);
});
}];
}
@end @end
@@ -77,6 +77,10 @@ static CGFloat RoundToScale(CGFloat v, CGFloat s) {
+ (UIImage *)iconRepresentationForPolicyType:(SBRScriptOriginPolicyType)policyType withConfiguration:(nonnull SBRScriptPolicyIconConfiguration *)configuration + (UIImage *)iconRepresentationForPolicyType:(SBRScriptOriginPolicyType)policyType withConfiguration:(nonnull SBRScriptPolicyIconConfiguration *)configuration
{ {
const CGSize size = [configuration size]; const CGSize size = [configuration size];
if (size.width < CGFLOAT_EPSILON || size.height < CGFLOAT_EPSILON) {
return nil;
}
UIFont *font = [UIFont boldSystemFontOfSize:size.height - 2]; UIFont *font = [UIFont boldSystemFontOfSize:size.height - 2];
NSDictionary<NSAttributedStringKey, id> *attrs = @{ NSDictionary<NSAttributedStringKey, id> *attrs = @{
NSFontAttributeName : font, NSFontAttributeName : font,
+12
View File
@@ -9,6 +9,18 @@ import Foundation
class SearchProvider class SearchProvider
{ {
// Build a provider from a URL template. Template should contain %q or %s
// which will be replaced with the sanitized query string.
static func fromTemplate(_ template: String) -> SearchProvider {
SearchProvider(resolver: { query in
let sanitized = query.sanitized()
let replaced = template
.replacingOccurrences(of: "%q", with: sanitized)
.replacingOccurrences(of: "%s", with: sanitized)
return URL(string: replaced) ?? URL(string: "https://google.com/search?q=\(sanitized)&gbv=1")!
})
}
static let google = SearchProvider(resolver: { query in static let google = SearchProvider(resolver: { query in
// gbv=1: no JS // gbv=1: no JS
URL(string: "https://google.com/search?q=\(query.sanitized())&gbv=1")! URL(string: "https://google.com/search?q=\(query.sanitized())&gbv=1")!
+3
View File
@@ -0,0 +1,3 @@
source "https://rubygems.org"
gem "fastlane", "2.237.0"
+244
View File
@@ -0,0 +1,244 @@
GEM
remote: https://rubygems.org/
specs:
CFPropertyList (3.0.8)
abbrev (0.1.2)
addressable (2.9.0)
public_suffix (>= 2.0.2, < 8.0)
artifactory (3.0.17)
atomos (0.1.3)
aws-eventstream (1.4.0)
aws-partitions (1.1274.0)
aws-sdk-core (3.254.0)
aws-eventstream (~> 1, >= 1.3.0)
aws-partitions (~> 1, >= 1.992.0)
aws-sigv4 (~> 1.9)
base64
bigdecimal
jmespath (~> 1, >= 1.6.1)
logger
aws-sdk-kms (1.130.0)
aws-sdk-core (~> 3, >= 3.254.0)
aws-sigv4 (~> 1.5)
aws-sdk-s3 (1.228.1)
aws-sdk-core (~> 3, >= 3.254.0)
aws-sdk-kms (~> 1)
aws-sigv4 (~> 1.5)
aws-sigv4 (1.12.1)
aws-eventstream (~> 1, >= 1.0.2)
babosa (1.0.4)
base64 (0.3.0)
benchmark (0.5.0)
bigdecimal (4.1.2)
claide (1.1.0)
colored (1.2)
colored2 (3.1.2)
commander (4.6.0)
highline (~> 2.0.0)
csv (3.3.6)
declarative (0.0.20)
digest-crc (0.7.0)
rake (>= 12.0.0, < 14.0.0)
domain_name (0.6.20240107)
dotenv (2.8.1)
emoji_regex (3.2.3)
excon (1.6.0)
logger
faraday (1.10.6)
faraday-em_http (~> 1.0)
faraday-em_synchrony (~> 1.0)
faraday-excon (~> 1.1)
faraday-httpclient (~> 1.0)
faraday-multipart (~> 1.0)
faraday-net_http (~> 1.0)
faraday-net_http_persistent (~> 1.0)
faraday-patron (~> 1.0)
faraday-rack (~> 1.0)
faraday-retry (~> 1.0)
ruby2_keywords (>= 0.0.4)
faraday-cookie_jar (0.0.8)
faraday (>= 0.8.0)
http-cookie (>= 1.0.0)
faraday-em_http (1.0.0)
faraday-em_synchrony (1.0.1)
faraday-excon (1.1.0)
faraday-httpclient (1.0.1)
faraday-multipart (1.2.0)
multipart-post (~> 2.0)
faraday-net_http (1.0.2)
faraday-net_http_persistent (1.2.0)
faraday-patron (1.0.0)
faraday-rack (1.0.0)
faraday-retry (1.0.4)
faraday_middleware (1.2.1)
faraday (~> 1.0)
fastimage (2.4.1)
fastlane (2.237.0)
CFPropertyList (>= 2.3, < 5.0.0)
abbrev (~> 0.1)
addressable (>= 2.9.0, < 3.0.0)
artifactory (~> 3.0)
aws-sdk-s3 (~> 1.197)
babosa (>= 1.0.3, < 2.0.0)
base64 (~> 0.2)
benchmark (>= 0.1.0)
bundler (>= 2.4.0, < 5.0.0)
colored (~> 1.2)
commander (~> 4.6)
csv (~> 3.3)
dotenv (>= 2.1.1, < 3.0.0)
emoji_regex (>= 0.1, < 4.0)
excon (>= 0.71.0, < 2.0.0)
faraday (~> 1.0)
faraday-cookie_jar (~> 0.0.6)
faraday_middleware (~> 1.0)
fastimage (>= 2.1.0, < 3.0.0)
fastlane-sirp (>= 1.1.0)
gh_inspector (>= 1.1.2, < 2.0.0)
google-apis-androidpublisher_v3 (~> 0.3)
google-apis-playcustomapp_v1 (~> 0.1)
google-cloud-env (>= 1.6.0, < 2.3.0)
google-cloud-storage (~> 1.31)
highline (~> 2.0)
http-cookie (~> 1.0.5)
json (< 3.0.0)
jwt (>= 2.10.3, < 4)
logger (>= 1.6, < 2.0)
mini_magick (>= 4.9.4, < 5.0.0)
multi_json (~> 1.12)
multipart-post (>= 2.0.0, < 3.0.0)
mutex_m (~> 0.3)
naturally (~> 2.2)
nkf (~> 0.2)
optparse (>= 0.1.1, < 1.0.0)
ostruct (>= 0.1.0)
plist (>= 3.1.0, < 4.0.0)
rubyzip (>= 2.0.0, < 3.0.0)
security (= 0.1.5)
simctl (~> 1.6.3)
terminal-notifier (>= 2.0.0, < 3.0.0)
terminal-table (~> 3)
tty-screen (>= 0.6.3, < 1.0.0)
tty-spinner (>= 0.8.0, < 1.0.0)
word_wrap (~> 1.0.0)
xcodeproj (>= 1.13.0, < 2.0.0)
xcpretty (~> 0.4.1)
xcpretty-travis-formatter (>= 0.0.3, < 2.0.0)
fastlane-sirp (1.1.0)
gh_inspector (1.1.3)
google-apis-androidpublisher_v3 (0.106.0)
google-apis-core (>= 0.15.0, < 2.a)
google-apis-core (0.18.0)
addressable (~> 2.5, >= 2.5.1)
googleauth (~> 1.9)
httpclient (>= 2.8.3, < 3.a)
mini_mime (~> 1.0)
mutex_m
representable (~> 3.0)
retriable (>= 2.0, < 4.a)
google-apis-iamcredentials_v1 (0.28.0)
google-apis-core (>= 0.15.0, < 2.a)
google-apis-playcustomapp_v1 (0.18.0)
google-apis-core (>= 0.15.0, < 2.a)
google-apis-storage_v1 (0.65.0)
google-apis-core (>= 0.15.0, < 2.a)
google-cloud-core (1.9.0)
google-cloud-env (>= 1.0, < 3.a)
google-cloud-errors (~> 1.0)
google-cloud-env (2.2.2)
base64 (~> 0.2)
faraday (>= 1.0, < 3.a)
google-cloud-errors (1.7.0)
google-cloud-storage (1.62.0)
addressable (~> 2.8)
digest-crc (~> 0.4)
google-apis-core (>= 0.18, < 2)
google-apis-iamcredentials_v1 (~> 0.18)
google-apis-storage_v1 (>= 0.42)
google-cloud-core (~> 1.6)
googleauth (~> 1.9)
mini_mime (~> 1.0)
google-logging-utils (0.2.0)
googleauth (1.17.2)
faraday (>= 1.0, < 3.a)
google-cloud-env (~> 2.2)
google-logging-utils (~> 0.1)
jwt (>= 1.4, < 4.0)
os (>= 0.9, < 2.0)
pstore (~> 0.1)
signet (>= 0.16, < 2.a)
highline (2.0.3)
http-cookie (1.0.8)
domain_name (~> 0.5)
httpclient (2.9.0)
mutex_m
jmespath (1.6.2)
json (2.21.1)
jwt (3.2.0)
base64
logger (1.7.0)
mini_magick (4.13.2)
mini_mime (1.1.5)
multi_json (1.21.1)
multipart-post (2.4.1)
mutex_m (0.3.0)
nanaimo (0.4.0)
naturally (2.3.0)
nkf (0.3.0)
optparse (0.8.1)
os (1.1.4)
ostruct (0.6.3)
plist (3.7.2)
pstore (0.2.1)
public_suffix (7.0.5)
rake (13.4.2)
representable (3.2.0)
declarative (< 0.1.0)
trailblazer-option (>= 0.1.1, < 0.2.0)
uber (< 0.2.0)
retriable (3.8.0)
rexml (3.4.4)
rouge (3.28.0)
ruby2_keywords (0.0.5)
rubyzip (2.4.1)
security (0.1.5)
signet (0.22.0)
addressable (~> 2.8)
faraday (>= 0.17.5, < 3.a)
jwt (>= 1.5, < 4.0)
simctl (1.6.10)
CFPropertyList
naturally
terminal-notifier (2.0.0)
terminal-table (3.0.2)
unicode-display_width (>= 1.1.1, < 3)
trailblazer-option (0.1.2)
tty-cursor (0.7.1)
tty-screen (0.8.2)
tty-spinner (0.9.3)
tty-cursor (~> 0.7)
uber (0.1.0)
unicode-display_width (2.6.0)
word_wrap (1.0.0)
xcodeproj (1.28.1)
CFPropertyList (>= 2.3.3, < 4.0)
atomos (~> 0.1.3)
base64
claide (>= 1.0.2, < 2.0)
colored2 (~> 3.1)
nanaimo (~> 0.4.0)
nkf
rexml (>= 3.3.6, < 4.0)
xcpretty (0.4.1)
rouge (~> 3.28.0)
xcpretty-travis-formatter (1.0.1)
xcpretty (~> 0.2, >= 0.0.7)
PLATFORMS
ruby
DEPENDENCIES
fastlane (= 2.237.0)
BUNDLED WITH
2.5.23
+35
View File
@@ -0,0 +1,35 @@
# Attractor
Attractor is a web browser for iOS and macOS prioritizing security and aggresive content filtering, and some nifty useful features.
![](Screenshots/main.png)
## Security Modes
Attractor aggresively filters unwanted web content and prevents arbitrary code execution (JavaScript) by default. I created a special browser for this purpose. Philosophically speaking, I believe the web should primarily be used as a document viewer (as originally intended by [Tim Berners-Lee](https://en.wikipedia.org/wiki/World_wide_web)). Interactive web applications are fine, but the *default* mode should be document viewing, which is why this security model exists.
Attractor operates in five "security modes":
1. **Alpha** (Default) All JavaScript is blocked, as well as web fonts and iframes.
2. **Bravo** Only embedded scripts are allowed. This means any JavaScript enclosed in `<script>` tags. Often this is enough to allow form validation to occur without issue.
3. **Charlie** Allows external JavaScript, but only from the same security origin. Meaning, if browsing `buzzert.net`, only external scripts from the domain `buzzert.net` (or subdomains) are allowed to execute.
4. **Delta** Allows external JavaScript, but only from a curated list of common CDNs.
5. **Echo** All scripts are allowed.
## Other features
- Dark mode (a very good one that uses low-level stylesheet injection to prevent flicker)
- Tab syncing
- Share via Email
- Reader Mode
- Arbitrary zoom scale
- User-defined scripts and stylesheets via UI
- Custom redirect rules
- Catalyst and iPadOS support
## Deployment
TestFlight deployment is handled by Fastlane and the Gitea workflow at `.gitea/workflows/testflight.yml`.
Required Gitea secrets match the local `.env.example`: `ASC_KEY_ID`, `ASC_ISSUER_ID`, `ASC_KEY`, `MATCH_PASSWORD`, and `MATCH_GIT_BASIC_AUTHORIZATION`.
Push a tag like `release/ios/v4.2` or `Attractor-4.2` to build and upload to TestFlight.
+74 -142
View File
@@ -3,7 +3,7 @@
archiveVersion = 1; archiveVersion = 1;
classes = { classes = {
}; };
objectVersion = 50; objectVersion = 70;
objects = { objects = {
/* Begin PBXBuildFile section */ /* Begin PBXBuildFile section */
@@ -27,10 +27,7 @@
1ADFF46C24C7DE54006DC7AE /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 1ADFF46A24C7DE54006DC7AE /* LaunchScreen.storyboard */; }; 1ADFF46C24C7DE54006DC7AE /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 1ADFF46A24C7DE54006DC7AE /* LaunchScreen.storyboard */; };
1ADFF47424C7DE9C006DC7AE /* BrowserViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1ADFF47324C7DE9C006DC7AE /* BrowserViewController.swift */; }; 1ADFF47424C7DE9C006DC7AE /* BrowserViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1ADFF47324C7DE9C006DC7AE /* BrowserViewController.swift */; };
1ADFF47924C7DFF8006DC7AE /* BrowserView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1ADFF47824C7DFF8006DC7AE /* BrowserView.swift */; }; 1ADFF47924C7DFF8006DC7AE /* BrowserView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1ADFF47824C7DFF8006DC7AE /* BrowserView.swift */; };
1ADFF48424C8C12F006DC7AE /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1ADFF48324C8C12F006DC7AE /* Foundation.framework */; };
1ADFF48D24C8C176006DC7AE /* SBRProcessBundleBridge.m in Sources */ = {isa = PBXBuildFile; fileRef = 1ADFF48C24C8C176006DC7AE /* SBRProcessBundleBridge.m */; }; 1ADFF48D24C8C176006DC7AE /* SBRProcessBundleBridge.m in Sources */ = {isa = PBXBuildFile; fileRef = 1ADFF48C24C8C176006DC7AE /* SBRProcessBundleBridge.m */; };
1ADFF4A724C8C271006DC7AE /* SBrowserProcessBundle.bundle in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = 1ADFF48124C8C12F006DC7AE /* SBrowserProcessBundle.bundle */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
1ADFF4AA24C8D477006DC7AE /* SBRProcessPlugin.m in Sources */ = {isa = PBXBuildFile; fileRef = 1ADFF4A924C8D477006DC7AE /* SBRProcessPlugin.m */; };
1ADFF4AE24C8ED32006DC7AE /* ResourcePolicyManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1ADFF4AD24C8ED32006DC7AE /* ResourcePolicyManager.swift */; }; 1ADFF4AE24C8ED32006DC7AE /* ResourcePolicyManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1ADFF4AD24C8ED32006DC7AE /* ResourcePolicyManager.swift */; };
1ADFF4C024CA6964006DC7AE /* URLBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1ADFF4BF24CA6964006DC7AE /* URLBar.swift */; }; 1ADFF4C024CA6964006DC7AE /* URLBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1ADFF4BF24CA6964006DC7AE /* URLBar.swift */; };
1ADFF4C324CA6AF6006DC7AE /* Geometry.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1ADFF4C224CA6AF6006DC7AE /* Geometry.swift */; }; 1ADFF4C324CA6AF6006DC7AE /* Geometry.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1ADFF4C224CA6AF6006DC7AE /* Geometry.swift */; };
@@ -40,11 +37,15 @@
1ADFF4CD24CBB0C8006DC7AE /* ScriptOriginPolicyViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1ADFF4CC24CBB0C8006DC7AE /* ScriptOriginPolicyViewController.swift */; }; 1ADFF4CD24CBB0C8006DC7AE /* ScriptOriginPolicyViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1ADFF4CC24CBB0C8006DC7AE /* ScriptOriginPolicyViewController.swift */; };
CD01D5A5254A10BB00189CDC /* TabBarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD01D5A4254A10BB00189CDC /* TabBarView.swift */; }; CD01D5A5254A10BB00189CDC /* TabBarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD01D5A4254A10BB00189CDC /* TabBarView.swift */; };
CD01D5AB254A206D00189CDC /* TabBarViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD01D5AA254A206D00189CDC /* TabBarViewController.swift */; }; CD01D5AB254A206D00189CDC /* TabBarViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD01D5AA254A206D00189CDC /* TabBarViewController.swift */; };
CD15332F297B6798009A7F3A /* HistoryBrowserViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD15332E297B6798009A7F3A /* HistoryBrowserViewController.swift */; };
CD153331297B6806009A7F3A /* HistoryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD153330297B6806009A7F3A /* HistoryView.swift */; };
CD16844D269E709400B8F8A5 /* Box.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD16844C269E709400B8F8A5 /* Box.swift */; }; CD16844D269E709400B8F8A5 /* Box.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD16844C269E709400B8F8A5 /* Box.swift */; };
CD19576D268BE95900E8089B /* GenericContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD19576C268BE95900E8089B /* GenericContentView.swift */; }; CD19576D268BE95900E8089B /* GenericContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD19576C268BE95900E8089B /* GenericContentView.swift */; };
CD361CF6271A3718006E9CA5 /* SBRScriptPolicy.m in Sources */ = {isa = PBXBuildFile; fileRef = CD361CF5271A3718006E9CA5 /* SBRScriptPolicy.m */; }; CD361CF6271A3718006E9CA5 /* SBRScriptPolicy.m in Sources */ = {isa = PBXBuildFile; fileRef = CD361CF5271A3718006E9CA5 /* SBRScriptPolicy.m */; };
CD470C4225DE056600AFBE0E /* BrowserViewController+WebKitDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD470C4125DE056600AFBE0E /* BrowserViewController+WebKitDelegate.swift */; }; CD470C4225DE056600AFBE0E /* BrowserViewController+WebKitDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD470C4125DE056600AFBE0E /* BrowserViewController+WebKitDelegate.swift */; };
CD470C4425DE070400AFBE0E /* BrowserViewController+Keyboard.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD470C4325DE070400AFBE0E /* BrowserViewController+Keyboard.swift */; }; CD470C4425DE070400AFBE0E /* BrowserViewController+Keyboard.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD470C4325DE070400AFBE0E /* BrowserViewController+Keyboard.swift */; };
CD4930D92E8B390200ADDE99 /* AddSearchEngineViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD4930D82E8B38FC00ADDE99 /* AddSearchEngineViewController.swift */; };
CD4930DB2E8B3F6500ADDE99 /* ManageSearchEnginesViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD4930DA2E8B3F5B00ADDE99 /* ManageSearchEnginesViewController.swift */; };
CD7313E22705349700053347 /* ScriptPolicyViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD7313E12705349700053347 /* ScriptPolicyViewController.swift */; }; CD7313E22705349700053347 /* ScriptPolicyViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD7313E12705349700053347 /* ScriptPolicyViewController.swift */; };
CD7313E4270534B800053347 /* ScriptPolicyViewControllerDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD7313E3270534B800053347 /* ScriptPolicyViewControllerDelegate.swift */; }; CD7313E4270534B800053347 /* ScriptPolicyViewControllerDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD7313E3270534B800053347 /* ScriptPolicyViewControllerDelegate.swift */; };
CD7A7E9D2686A9A500E20BA3 /* SettingsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD7A7E9C2686A9A500E20BA3 /* SettingsViewController.swift */; }; CD7A7E9D2686A9A500E20BA3 /* SettingsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD7A7E9C2686A9A500E20BA3 /* SettingsViewController.swift */; };
@@ -57,10 +58,12 @@
CD853BCE24E7763900D2BDCC /* BrowserHistory.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD853BCD24E7763900D2BDCC /* BrowserHistory.swift */; }; CD853BCE24E7763900D2BDCC /* BrowserHistory.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD853BCD24E7763900D2BDCC /* BrowserHistory.swift */; };
CD853BD124E778B800D2BDCC /* History.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = CD853BCF24E778B800D2BDCC /* History.xcdatamodeld */; }; CD853BD124E778B800D2BDCC /* History.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = CD853BCF24E778B800D2BDCC /* History.xcdatamodeld */; };
CD853BD424E77BF900D2BDCC /* HistoryItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD853BD324E77BF900D2BDCC /* HistoryItem.swift */; }; CD853BD424E77BF900D2BDCC /* HistoryItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD853BD324E77BF900D2BDCC /* HistoryItem.swift */; };
CD8ACBC22DC9A2F7008BF856 /* Hacks.m in Sources */ = {isa = PBXBuildFile; fileRef = CD8ACBC12DC9A2F7008BF856 /* Hacks.m */; };
CD8DBE7B2A85D892006A0FE0 /* LayoutLatch.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD8DBE7A2A85D892006A0FE0 /* LayoutLatch.swift */; };
CD936A3B289DB3380093A1AC /* TabInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD936A3A289DB3380093A1AC /* TabInfo.swift */; }; CD936A3B289DB3380093A1AC /* TabInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD936A3A289DB3380093A1AC /* TabInfo.swift */; };
CD936A3D289DB88B0093A1AC /* UIView+Utils.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD936A3C289DB88B0093A1AC /* UIView+Utils.swift */; }; CD936A3D289DB88B0093A1AC /* UIView+Utils.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD936A3C289DB88B0093A1AC /* UIView+Utils.swift */; };
CD97CF9225D5BE6F00288FEE /* NavigationControlsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD97CF9125D5BE6F00288FEE /* NavigationControlsView.swift */; }; CD97CF9225D5BE6F00288FEE /* NavigationControlsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD97CF9125D5BE6F00288FEE /* NavigationControlsView.swift */; };
CD9B88C2272201E900DAAB7E /* SBRScriptPolicy.m in Sources */ = {isa = PBXBuildFile; fileRef = CD361CF5271A3718006E9CA5 /* SBRScriptPolicy.m */; }; CDACE18C2C5B09F00053AB29 /* StringUtilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = CDACE18B2C5B09F00053AB29 /* StringUtilities.swift */; };
CDAD9CE8263A2DF200FF7199 /* DocumentControlsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CDAD9CE7263A2DF200FF7199 /* DocumentControlsView.swift */; }; CDAD9CE8263A2DF200FF7199 /* DocumentControlsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CDAD9CE7263A2DF200FF7199 /* DocumentControlsView.swift */; };
CDAD9CEA263A318F00FF7199 /* ShareableURL.swift in Sources */ = {isa = PBXBuildFile; fileRef = CDAD9CE9263A318F00FF7199 /* ShareableURL.swift */; }; CDAD9CEA263A318F00FF7199 /* ShareableURL.swift in Sources */ = {isa = PBXBuildFile; fileRef = CDAD9CE9263A318F00FF7199 /* ShareableURL.swift */; };
CDB6807D28B446FC007D787E /* ReaderSceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = CDB6807C28B446FC007D787E /* ReaderSceneDelegate.swift */; }; CDB6807D28B446FC007D787E /* ReaderSceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = CDB6807C28B446FC007D787E /* ReaderSceneDelegate.swift */; };
@@ -83,16 +86,6 @@
CDF34690276C14BD00FB3141 /* CodeEditorSettingsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CDF3468F276C14BD00FB3141 /* CodeEditorSettingsViewController.swift */; }; CDF34690276C14BD00FB3141 /* CodeEditorSettingsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CDF3468F276C14BD00FB3141 /* CodeEditorSettingsViewController.swift */; };
/* End PBXBuildFile section */ /* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
1ADFF48E24C8C230006DC7AE /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 1ADFF45424C7DE53006DC7AE /* Project object */;
proxyType = 1;
remoteGlobalIDString = 1ADFF48024C8C12F006DC7AE;
remoteInfo = SBrowserProcessBundle;
};
/* End PBXContainerItemProxy section */
/* Begin PBXCopyFilesBuildPhase section */ /* Begin PBXCopyFilesBuildPhase section */
1ADFF4A124C8C25B006DC7AE /* Embed App Extensions */ = { 1ADFF4A124C8C25B006DC7AE /* Embed App Extensions */ = {
isa = PBXCopyFilesBuildPhase; isa = PBXCopyFilesBuildPhase;
@@ -100,7 +93,6 @@
dstPath = ""; dstPath = "";
dstSubfolderSpec = 13; dstSubfolderSpec = 13;
files = ( files = (
1ADFF4A724C8C271006DC7AE /* SBrowserProcessBundle.bundle in Embed App Extensions */,
); );
name = "Embed App Extensions"; name = "Embed App Extensions";
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
@@ -131,16 +123,10 @@
1ADFF47324C7DE9C006DC7AE /* BrowserViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BrowserViewController.swift; sourceTree = "<group>"; }; 1ADFF47324C7DE9C006DC7AE /* BrowserViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BrowserViewController.swift; sourceTree = "<group>"; };
1ADFF47524C7DF20006DC7AE /* SBrowser.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = SBrowser.entitlements; sourceTree = "<group>"; }; 1ADFF47524C7DF20006DC7AE /* SBrowser.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = SBrowser.entitlements; sourceTree = "<group>"; };
1ADFF47824C7DFF8006DC7AE /* BrowserView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BrowserView.swift; sourceTree = "<group>"; }; 1ADFF47824C7DFF8006DC7AE /* BrowserView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BrowserView.swift; sourceTree = "<group>"; };
1ADFF48124C8C12F006DC7AE /* SBrowserProcessBundle.bundle */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = SBrowserProcessBundle.bundle; sourceTree = BUILT_PRODUCTS_DIR; };
1ADFF48324C8C12F006DC7AE /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; 1ADFF48324C8C12F006DC7AE /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; };
1ADFF48624C8C12F006DC7AE /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
1ADFF48A24C8C176006DC7AE /* SBrowser-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "SBrowser-Bridging-Header.h"; sourceTree = "<group>"; }; 1ADFF48A24C8C176006DC7AE /* SBrowser-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "SBrowser-Bridging-Header.h"; sourceTree = "<group>"; };
1ADFF48B24C8C176006DC7AE /* SBRProcessBundleBridge.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SBRProcessBundleBridge.h; sourceTree = "<group>"; }; 1ADFF48B24C8C176006DC7AE /* SBRProcessBundleBridge.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SBRProcessBundleBridge.h; sourceTree = "<group>"; };
1ADFF48C24C8C176006DC7AE /* SBRProcessBundleBridge.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SBRProcessBundleBridge.m; sourceTree = "<group>"; }; 1ADFF48C24C8C176006DC7AE /* SBRProcessBundleBridge.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SBRProcessBundleBridge.m; sourceTree = "<group>"; };
1ADFF4A824C8D476006DC7AE /* SBRProcessPlugin.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SBRProcessPlugin.h; sourceTree = "<group>"; };
1ADFF4A924C8D477006DC7AE /* SBRProcessPlugin.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SBRProcessPlugin.m; sourceTree = "<group>"; };
1ADFF4AB24C8DF62006DC7AE /* SBRWebProcessDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SBRWebProcessDelegate.h; sourceTree = "<group>"; };
1ADFF4AC24C8DFEE006DC7AE /* SBRWebProcessProxy.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SBRWebProcessProxy.h; sourceTree = "<group>"; };
1ADFF4AD24C8ED32006DC7AE /* ResourcePolicyManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ResourcePolicyManager.swift; sourceTree = "<group>"; }; 1ADFF4AD24C8ED32006DC7AE /* ResourcePolicyManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ResourcePolicyManager.swift; sourceTree = "<group>"; };
1ADFF4BF24CA6964006DC7AE /* URLBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URLBar.swift; sourceTree = "<group>"; }; 1ADFF4BF24CA6964006DC7AE /* URLBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URLBar.swift; sourceTree = "<group>"; };
1ADFF4C224CA6AF6006DC7AE /* Geometry.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Geometry.swift; sourceTree = "<group>"; }; 1ADFF4C224CA6AF6006DC7AE /* Geometry.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Geometry.swift; sourceTree = "<group>"; };
@@ -150,12 +136,17 @@
1ADFF4CC24CBB0C8006DC7AE /* ScriptOriginPolicyViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScriptOriginPolicyViewController.swift; sourceTree = "<group>"; }; 1ADFF4CC24CBB0C8006DC7AE /* ScriptOriginPolicyViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScriptOriginPolicyViewController.swift; sourceTree = "<group>"; };
CD01D5A4254A10BB00189CDC /* TabBarView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabBarView.swift; sourceTree = "<group>"; }; CD01D5A4254A10BB00189CDC /* TabBarView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabBarView.swift; sourceTree = "<group>"; };
CD01D5AA254A206D00189CDC /* TabBarViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabBarViewController.swift; sourceTree = "<group>"; }; CD01D5AA254A206D00189CDC /* TabBarViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabBarViewController.swift; sourceTree = "<group>"; };
CD15332E297B6798009A7F3A /* HistoryBrowserViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HistoryBrowserViewController.swift; sourceTree = "<group>"; };
CD153330297B6806009A7F3A /* HistoryView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HistoryView.swift; sourceTree = "<group>"; };
CD16844C269E709400B8F8A5 /* Box.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Box.swift; sourceTree = "<group>"; }; CD16844C269E709400B8F8A5 /* Box.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Box.swift; sourceTree = "<group>"; };
CD19576C268BE95900E8089B /* GenericContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GenericContentView.swift; sourceTree = "<group>"; }; CD19576C268BE95900E8089B /* GenericContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GenericContentView.swift; sourceTree = "<group>"; };
CD361CF4271A3718006E9CA5 /* SBRScriptPolicy.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SBRScriptPolicy.h; sourceTree = "<group>"; }; CD361CF4271A3718006E9CA5 /* SBRScriptPolicy.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SBRScriptPolicy.h; sourceTree = "<group>"; };
CD361CF5271A3718006E9CA5 /* SBRScriptPolicy.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SBRScriptPolicy.m; sourceTree = "<group>"; }; CD361CF5271A3718006E9CA5 /* SBRScriptPolicy.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SBRScriptPolicy.m; sourceTree = "<group>"; };
CD3D6CED2DA9F8910099667F /* WebKitDefines.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = WebKitDefines.h; sourceTree = "<group>"; };
CD470C4125DE056600AFBE0E /* BrowserViewController+WebKitDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "BrowserViewController+WebKitDelegate.swift"; sourceTree = "<group>"; }; CD470C4125DE056600AFBE0E /* BrowserViewController+WebKitDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "BrowserViewController+WebKitDelegate.swift"; sourceTree = "<group>"; };
CD470C4325DE070400AFBE0E /* BrowserViewController+Keyboard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "BrowserViewController+Keyboard.swift"; sourceTree = "<group>"; }; CD470C4325DE070400AFBE0E /* BrowserViewController+Keyboard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "BrowserViewController+Keyboard.swift"; sourceTree = "<group>"; };
CD4930D82E8B38FC00ADDE99 /* AddSearchEngineViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddSearchEngineViewController.swift; sourceTree = "<group>"; };
CD4930DA2E8B3F5B00ADDE99 /* ManageSearchEnginesViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ManageSearchEnginesViewController.swift; sourceTree = "<group>"; };
CD7313E12705349700053347 /* ScriptPolicyViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScriptPolicyViewController.swift; sourceTree = "<group>"; }; CD7313E12705349700053347 /* ScriptPolicyViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScriptPolicyViewController.swift; sourceTree = "<group>"; };
CD7313E3270534B800053347 /* ScriptPolicyViewControllerDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScriptPolicyViewControllerDelegate.swift; sourceTree = "<group>"; }; CD7313E3270534B800053347 /* ScriptPolicyViewControllerDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScriptPolicyViewControllerDelegate.swift; sourceTree = "<group>"; };
CD7A7E9C2686A9A500E20BA3 /* SettingsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsViewController.swift; sourceTree = "<group>"; }; CD7A7E9C2686A9A500E20BA3 /* SettingsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsViewController.swift; sourceTree = "<group>"; };
@@ -168,9 +159,13 @@
CD853BCD24E7763900D2BDCC /* BrowserHistory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BrowserHistory.swift; sourceTree = "<group>"; }; CD853BCD24E7763900D2BDCC /* BrowserHistory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BrowserHistory.swift; sourceTree = "<group>"; };
CD853BD024E778B800D2BDCC /* History.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = History.xcdatamodel; sourceTree = "<group>"; }; CD853BD024E778B800D2BDCC /* History.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = History.xcdatamodel; sourceTree = "<group>"; };
CD853BD324E77BF900D2BDCC /* HistoryItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HistoryItem.swift; sourceTree = "<group>"; }; CD853BD324E77BF900D2BDCC /* HistoryItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HistoryItem.swift; sourceTree = "<group>"; };
CD8ACBC02DC9A2F7008BF856 /* Hacks.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Hacks.h; sourceTree = "<group>"; };
CD8ACBC12DC9A2F7008BF856 /* Hacks.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = Hacks.m; sourceTree = "<group>"; };
CD8DBE7A2A85D892006A0FE0 /* LayoutLatch.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LayoutLatch.swift; sourceTree = "<group>"; };
CD936A3A289DB3380093A1AC /* TabInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabInfo.swift; sourceTree = "<group>"; }; CD936A3A289DB3380093A1AC /* TabInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabInfo.swift; sourceTree = "<group>"; };
CD936A3C289DB88B0093A1AC /* UIView+Utils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIView+Utils.swift"; sourceTree = "<group>"; }; CD936A3C289DB88B0093A1AC /* UIView+Utils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIView+Utils.swift"; sourceTree = "<group>"; };
CD97CF9125D5BE6F00288FEE /* NavigationControlsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationControlsView.swift; sourceTree = "<group>"; }; CD97CF9125D5BE6F00288FEE /* NavigationControlsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationControlsView.swift; sourceTree = "<group>"; };
CDACE18B2C5B09F00053AB29 /* StringUtilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StringUtilities.swift; sourceTree = "<group>"; };
CDAD9CE7263A2DF200FF7199 /* DocumentControlsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DocumentControlsView.swift; sourceTree = "<group>"; }; CDAD9CE7263A2DF200FF7199 /* DocumentControlsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DocumentControlsView.swift; sourceTree = "<group>"; };
CDAD9CE9263A318F00FF7199 /* ShareableURL.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareableURL.swift; sourceTree = "<group>"; }; CDAD9CE9263A318F00FF7199 /* ShareableURL.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareableURL.swift; sourceTree = "<group>"; };
CDB6807C28B446FC007D787E /* ReaderSceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReaderSceneDelegate.swift; sourceTree = "<group>"; }; CDB6807C28B446FC007D787E /* ReaderSceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReaderSceneDelegate.swift; sourceTree = "<group>"; };
@@ -193,6 +188,10 @@
CDF3468F276C14BD00FB3141 /* CodeEditorSettingsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CodeEditorSettingsViewController.swift; sourceTree = "<group>"; }; CDF3468F276C14BD00FB3141 /* CodeEditorSettingsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CodeEditorSettingsViewController.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */ /* End PBXFileReference section */
/* Begin PBXFileSystemSynchronizedRootGroup section */
CDB73F3F2DA9F2F100DF4FAB /* WebKit Headers */ = {isa = PBXFileSystemSynchronizedRootGroup; explicitFileTypes = {}; explicitFolders = (); path = "WebKit Headers"; sourceTree = "<group>"; };
/* End PBXFileSystemSynchronizedRootGroup section */
/* Begin PBXFrameworksBuildPhase section */ /* Begin PBXFrameworksBuildPhase section */
1ADFF45924C7DE53006DC7AE /* Frameworks */ = { 1ADFF45924C7DE53006DC7AE /* Frameworks */ = {
isa = PBXFrameworksBuildPhase; isa = PBXFrameworksBuildPhase;
@@ -201,14 +200,6 @@
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
}; };
1ADFF47E24C8C12F006DC7AE /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
1ADFF48424C8C12F006DC7AE /* Foundation.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */ /* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */ /* Begin PBXGroup section */
@@ -275,7 +266,7 @@
children = ( children = (
CDB6808828B4569F007D787E /* LICENSE.txt */, CDB6808828B4569F007D787E /* LICENSE.txt */,
1ADFF45E24C7DE53006DC7AE /* App */, 1ADFF45E24C7DE53006DC7AE /* App */,
1ADFF48524C8C12F006DC7AE /* SBrowserProcessBundle */, CDB73F3F2DA9F2F100DF4FAB /* WebKit Headers */,
1ADFF48224C8C12F006DC7AE /* Frameworks */, 1ADFF48224C8C12F006DC7AE /* Frameworks */,
1ADFF45D24C7DE53006DC7AE /* Products */, 1ADFF45D24C7DE53006DC7AE /* Products */,
); );
@@ -285,7 +276,6 @@
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
1ADFF45C24C7DE53006DC7AE /* Attractor.app */, 1ADFF45C24C7DE53006DC7AE /* Attractor.app */,
1ADFF48124C8C12F006DC7AE /* SBrowserProcessBundle.bundle */,
); );
name = Products; name = Products;
sourceTree = "<group>"; sourceTree = "<group>";
@@ -302,6 +292,7 @@
CDCE2662251AA7FC007FE92A /* Document Controls UI */, CDCE2662251AA7FC007FE92A /* Document Controls UI */,
1AD3104125254FA300A4A952 /* Find on Page */, 1AD3104125254FA300A4A952 /* Find on Page */,
CD7F2133265DACEE0001D042 /* Hacks */, CD7F2133265DACEE0001D042 /* Hacks */,
CD15332D297B6784009A7F3A /* History UI */,
CDC5DA3C25DB7A5500BA8D99 /* Reader View */, CDC5DA3C25DB7A5500BA8D99 /* Reader View */,
CDB6807B28B4456B007D787E /* Scene Delegates */, CDB6807B28B4456B007D787E /* Scene Delegates */,
1ADFF4CE24CBBCBD006DC7AE /* Script Policy UI */, 1ADFF4CE24CBBCBD006DC7AE /* Script Policy UI */,
@@ -326,6 +317,7 @@
1ADFF46824C7DE54006DC7AE /* Assets.xcassets */, 1ADFF46824C7DE54006DC7AE /* Assets.xcassets */,
1ADFF46A24C7DE54006DC7AE /* LaunchScreen.storyboard */, 1ADFF46A24C7DE54006DC7AE /* LaunchScreen.storyboard */,
1ADFF46D24C7DE54006DC7AE /* Info.plist */, 1ADFF46D24C7DE54006DC7AE /* Info.plist */,
CD3D6CED2DA9F8910099667F /* WebKitDefines.h */,
); );
path = "Supporting Files"; path = "Supporting Files";
sourceTree = "<group>"; sourceTree = "<group>";
@@ -358,18 +350,6 @@
name = Frameworks; name = Frameworks;
sourceTree = "<group>"; sourceTree = "<group>";
}; };
1ADFF48524C8C12F006DC7AE /* SBrowserProcessBundle */ = {
isa = PBXGroup;
children = (
1ADFF48624C8C12F006DC7AE /* Info.plist */,
1ADFF4AB24C8DF62006DC7AE /* SBRWebProcessDelegate.h */,
1ADFF4AC24C8DFEE006DC7AE /* SBRWebProcessProxy.h */,
1ADFF4A824C8D476006DC7AE /* SBRProcessPlugin.h */,
1ADFF4A924C8D477006DC7AE /* SBRProcessPlugin.m */,
);
path = SBrowserProcessBundle;
sourceTree = "<group>";
};
1ADFF4AF24C92E2F006DC7AE /* Web Process Bundle Bridge */ = { 1ADFF4AF24C92E2F006DC7AE /* Web Process Bundle Bridge */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
@@ -391,6 +371,8 @@
1AB88F0524D4D3A90006F850 /* UIGestureRecognizer+Actions.swift */, 1AB88F0524D4D3A90006F850 /* UIGestureRecognizer+Actions.swift */,
CDEDD8A925D62ADB00862605 /* UITraitCollection+MacLike.swift */, CDEDD8A925D62ADB00862605 /* UITraitCollection+MacLike.swift */,
CD936A3C289DB88B0093A1AC /* UIView+Utils.swift */, CD936A3C289DB88B0093A1AC /* UIView+Utils.swift */,
CD8DBE7A2A85D892006A0FE0 /* LayoutLatch.swift */,
CDACE18B2C5B09F00053AB29 /* StringUtilities.swift */,
); );
path = Utilities; path = Utilities;
sourceTree = "<group>"; sourceTree = "<group>";
@@ -406,6 +388,15 @@
path = "Script Policy UI"; path = "Script Policy UI";
sourceTree = "<group>"; sourceTree = "<group>";
}; };
CD15332D297B6784009A7F3A /* History UI */ = {
isa = PBXGroup;
children = (
CD15332E297B6798009A7F3A /* HistoryBrowserViewController.swift */,
CD153330297B6806009A7F3A /* HistoryView.swift */,
);
path = "History UI";
sourceTree = "<group>";
};
CD7A7E9B2686A99600E20BA3 /* Amber */ = { CD7A7E9B2686A99600E20BA3 /* Amber */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
@@ -426,6 +417,8 @@
CD7F2133265DACEE0001D042 /* Hacks */ = { CD7F2133265DACEE0001D042 /* Hacks */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
CD8ACBC02DC9A2F7008BF856 /* Hacks.h */,
CD8ACBC12DC9A2F7008BF856 /* Hacks.m */,
CD7F2134265DAD010001D042 /* MFMailComposeViewControllerFix.m */, CD7F2134265DAD010001D042 /* MFMailComposeViewControllerFix.m */,
); );
path = Hacks; path = Hacks;
@@ -488,6 +481,8 @@
CD7A7E9C2686A9A500E20BA3 /* SettingsViewController.swift */, CD7A7E9C2686A9A500E20BA3 /* SettingsViewController.swift */,
CD7A7E9E2686B29100E20BA3 /* GeneralSettingsViewController.swift */, CD7A7E9E2686B29100E20BA3 /* GeneralSettingsViewController.swift */,
CD7A7EA02686B2E600E20BA3 /* RedirectRulesSettingsViewController.swift */, CD7A7EA02686B2E600E20BA3 /* RedirectRulesSettingsViewController.swift */,
CD4930D82E8B38FC00ADDE99 /* AddSearchEngineViewController.swift */,
CD4930DA2E8B3F5B00ADDE99 /* ManageSearchEnginesViewController.swift */,
CDF3468F276C14BD00FB3141 /* CodeEditorSettingsViewController.swift */, CDF3468F276C14BD00FB3141 /* CodeEditorSettingsViewController.swift */,
); );
path = Settings; path = Settings;
@@ -516,30 +511,12 @@
buildRules = ( buildRules = (
); );
dependencies = ( dependencies = (
1ADFF48F24C8C230006DC7AE /* PBXTargetDependency */,
); );
name = App; name = App;
productName = SBrowser; productName = SBrowser;
productReference = 1ADFF45C24C7DE53006DC7AE /* Attractor.app */; productReference = 1ADFF45C24C7DE53006DC7AE /* Attractor.app */;
productType = "com.apple.product-type.application"; productType = "com.apple.product-type.application";
}; };
1ADFF48024C8C12F006DC7AE /* SBrowserProcessBundle */ = {
isa = PBXNativeTarget;
buildConfigurationList = 1ADFF48724C8C12F006DC7AE /* Build configuration list for PBXNativeTarget "SBrowserProcessBundle" */;
buildPhases = (
1ADFF47D24C8C12F006DC7AE /* Sources */,
1ADFF47E24C8C12F006DC7AE /* Frameworks */,
1ADFF47F24C8C12F006DC7AE /* Resources */,
);
buildRules = (
);
dependencies = (
);
name = SBrowserProcessBundle;
productName = SBrowserProcessBundle;
productReference = 1ADFF48124C8C12F006DC7AE /* SBrowserProcessBundle.bundle */;
productType = "com.apple.product-type.bundle";
};
/* End PBXNativeTarget section */ /* End PBXNativeTarget section */
/* Begin PBXProject section */ /* Begin PBXProject section */
@@ -553,9 +530,6 @@
CreatedOnToolsVersion = 12.0; CreatedOnToolsVersion = 12.0;
LastSwiftMigration = 1200; LastSwiftMigration = 1200;
}; };
1ADFF48024C8C12F006DC7AE = {
CreatedOnToolsVersion = 12.0;
};
}; };
}; };
buildConfigurationList = 1ADFF45724C7DE53006DC7AE /* Build configuration list for PBXProject "SBrowser" */; buildConfigurationList = 1ADFF45724C7DE53006DC7AE /* Build configuration list for PBXProject "SBrowser" */;
@@ -572,7 +546,6 @@
projectRoot = ""; projectRoot = "";
targets = ( targets = (
1ADFF45B24C7DE53006DC7AE /* App */, 1ADFF45B24C7DE53006DC7AE /* App */,
1ADFF48024C8C12F006DC7AE /* SBrowserProcessBundle */,
); );
}; };
/* End PBXProject section */ /* End PBXProject section */
@@ -593,13 +566,6 @@
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
}; };
1ADFF47F24C8C12F006DC7AE /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXResourcesBuildPhase section */ /* End PBXResourcesBuildPhase section */
/* Begin PBXSourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */
@@ -609,8 +575,10 @@
files = ( files = (
1ADFF46024C7DE53006DC7AE /* AppDelegate.swift in Sources */, 1ADFF46024C7DE53006DC7AE /* AppDelegate.swift in Sources */,
1AD3104325254FB900A4A952 /* FindOnPageViewController.swift in Sources */, 1AD3104325254FB900A4A952 /* FindOnPageViewController.swift in Sources */,
CD4930D92E8B390200ADDE99 /* AddSearchEngineViewController.swift in Sources */,
1A03811424E73EB300826501 /* SegmentedReliefButton.swift in Sources */, 1A03811424E73EB300826501 /* SegmentedReliefButton.swift in Sources */,
1A03811024E71CF000826501 /* ReliefButton.swift in Sources */, 1A03811024E71CF000826501 /* ReliefButton.swift in Sources */,
CD8ACBC22DC9A2F7008BF856 /* Hacks.m in Sources */,
1A03811224E71EAA00826501 /* GradientView.swift in Sources */, 1A03811224E71EAA00826501 /* GradientView.swift in Sources */,
1ADFF4C024CA6964006DC7AE /* URLBar.swift in Sources */, 1ADFF4C024CA6964006DC7AE /* URLBar.swift in Sources */,
CDAD9CEA263A318F00FF7199 /* ShareableURL.swift in Sources */, CDAD9CEA263A318F00FF7199 /* ShareableURL.swift in Sources */,
@@ -634,6 +602,7 @@
CDB6807D28B446FC007D787E /* ReaderSceneDelegate.swift in Sources */, CDB6807D28B446FC007D787E /* ReaderSceneDelegate.swift in Sources */,
CDD0522425F8055700DD1771 /* SearchProvider.swift in Sources */, CDD0522425F8055700DD1771 /* SearchProvider.swift in Sources */,
CD853BD424E77BF900D2BDCC /* HistoryItem.swift in Sources */, CD853BD424E77BF900D2BDCC /* HistoryItem.swift in Sources */,
CD15332F297B6798009A7F3A /* HistoryBrowserViewController.swift in Sources */,
1ADFF48D24C8C176006DC7AE /* SBRProcessBundleBridge.m in Sources */, 1ADFF48D24C8C176006DC7AE /* SBRProcessBundleBridge.m in Sources */,
1AB88F0624D4D3A90006F850 /* UIGestureRecognizer+Actions.swift in Sources */, 1AB88F0624D4D3A90006F850 /* UIGestureRecognizer+Actions.swift in Sources */,
CD7313E4270534B800053347 /* ScriptPolicyViewControllerDelegate.swift in Sources */, CD7313E4270534B800053347 /* ScriptPolicyViewControllerDelegate.swift in Sources */,
@@ -641,6 +610,8 @@
CD16844D269E709400B8F8A5 /* Box.swift in Sources */, CD16844D269E709400B8F8A5 /* Box.swift in Sources */,
CD853BCE24E7763900D2BDCC /* BrowserHistory.swift in Sources */, CD853BCE24E7763900D2BDCC /* BrowserHistory.swift in Sources */,
1A03810B24E71C5600826501 /* ToolbarButtonContainerView.swift in Sources */, 1A03810B24E71C5600826501 /* ToolbarButtonContainerView.swift in Sources */,
CD8DBE7B2A85D892006A0FE0 /* LayoutLatch.swift in Sources */,
CD4930DB2E8B3F6500ADDE99 /* ManageSearchEnginesViewController.swift in Sources */,
CD7A7EA12686B2E600E20BA3 /* RedirectRulesSettingsViewController.swift in Sources */, CD7A7EA12686B2E600E20BA3 /* RedirectRulesSettingsViewController.swift in Sources */,
1ADFF4CB24CB8278006DC7AE /* ScriptControllerIconView.swift in Sources */, 1ADFF4CB24CB8278006DC7AE /* ScriptControllerIconView.swift in Sources */,
CD7A8915251975B70075991E /* AutocompleteViewController.swift in Sources */, CD7A8915251975B70075991E /* AutocompleteViewController.swift in Sources */,
@@ -654,6 +625,7 @@
CDAD9CE8263A2DF200FF7199 /* DocumentControlsView.swift in Sources */, CDAD9CE8263A2DF200FF7199 /* DocumentControlsView.swift in Sources */,
1ADFF4C924CA793E006DC7AE /* ToolbarViewController.swift in Sources */, 1ADFF4C924CA793E006DC7AE /* ToolbarViewController.swift in Sources */,
CD7A89172519872D0075991E /* KeyboardShortcuts.swift in Sources */, CD7A89172519872D0075991E /* KeyboardShortcuts.swift in Sources */,
CDACE18C2C5B09F00053AB29 /* StringUtilities.swift in Sources */,
1ADFF4CD24CBB0C8006DC7AE /* ScriptOriginPolicyViewController.swift in Sources */, 1ADFF4CD24CBB0C8006DC7AE /* ScriptOriginPolicyViewController.swift in Sources */,
CDE6A30425F023BC00E912A4 /* AmberSettingsViewController.swift in Sources */, CDE6A30425F023BC00E912A4 /* AmberSettingsViewController.swift in Sources */,
1A14FC2824D26749009B3F83 /* Tab.swift in Sources */, 1A14FC2824D26749009B3F83 /* Tab.swift in Sources */,
@@ -663,6 +635,7 @@
CD01D5AB254A206D00189CDC /* TabBarViewController.swift in Sources */, CD01D5AB254A206D00189CDC /* TabBarViewController.swift in Sources */,
1ADFF47924C7DFF8006DC7AE /* BrowserView.swift in Sources */, 1ADFF47924C7DFF8006DC7AE /* BrowserView.swift in Sources */,
CDCE2664251AA80F007FE92A /* DocumentControlViewController.swift in Sources */, CDCE2664251AA80F007FE92A /* DocumentControlViewController.swift in Sources */,
CD153331297B6806009A7F3A /* HistoryView.swift in Sources */,
CDF3468E276C105900FB3141 /* SettingsSceneDelegate.swift in Sources */, CDF3468E276C105900FB3141 /* SettingsSceneDelegate.swift in Sources */,
1AB88EFF24D3BBA50006F850 /* TabPickerViewController.swift in Sources */, 1AB88EFF24D3BBA50006F850 /* TabPickerViewController.swift in Sources */,
CD19576D268BE95900E8089B /* GenericContentView.swift in Sources */, CD19576D268BE95900E8089B /* GenericContentView.swift in Sources */,
@@ -672,25 +645,8 @@
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
}; };
1ADFF47D24C8C12F006DC7AE /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
CD9B88C2272201E900DAAB7E /* SBRScriptPolicy.m in Sources */,
1ADFF4AA24C8D477006DC7AE /* SBRProcessPlugin.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */ /* End PBXSourcesBuildPhase section */
/* Begin PBXTargetDependency section */
1ADFF48F24C8C230006DC7AE /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = 1ADFF48024C8C12F006DC7AE /* SBrowserProcessBundle */;
targetProxy = 1ADFF48E24C8C230006DC7AE /* PBXContainerItemProxy */;
};
/* End PBXTargetDependency section */
/* Begin PBXVariantGroup section */ /* Begin PBXVariantGroup section */
1ADFF46A24C7DE54006DC7AE /* LaunchScreen.storyboard */ = { 1ADFF46A24C7DE54006DC7AE /* LaunchScreen.storyboard */ = {
isa = PBXVariantGroup; isa = PBXVariantGroup;
@@ -756,11 +712,12 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES; GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 15.0; HEADER_SEARCH_PATHS = "\"WebKit Headers\"";
IPHONEOS_DEPLOYMENT_TARGET = 18.0;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES; MTL_FAST_MATH = YES;
ONLY_ACTIVE_ARCH = YES; ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos.internal; SDKROOT = iphoneos;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_OPTIMIZATION_LEVEL = "-Onone";
}; };
@@ -813,10 +770,11 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES; GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 15.0; HEADER_SEARCH_PATHS = "\"WebKit Headers\"";
IPHONEOS_DEPLOYMENT_TARGET = 18.0;
MTL_ENABLE_DEBUG_INFO = NO; MTL_ENABLE_DEBUG_INFO = NO;
MTL_FAST_MATH = YES; MTL_FAST_MATH = YES;
SDKROOT = iphoneos.internal; SDKROOT = iphoneos;
SWIFT_COMPILATION_MODE = wholemodule; SWIFT_COMPILATION_MODE = wholemodule;
SWIFT_OPTIMIZATION_LEVEL = "-O"; SWIFT_OPTIMIZATION_LEVEL = "-O";
VALIDATE_PRODUCT = YES; VALIDATE_PRODUCT = YES;
@@ -830,19 +788,27 @@
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = "App/Supporting Files/SBrowser.entitlements"; CODE_SIGN_ENTITLEMENTS = "App/Supporting Files/SBrowser.entitlements";
CODE_SIGN_IDENTITY = "-"; CODE_SIGN_IDENTITY = "Apple Development";
CURRENT_PROJECT_VERSION = 4; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 6;
DEAD_CODE_STRIPPING = YES; DEAD_CODE_STRIPPING = YES;
DEVELOPMENT_TEAM = 3SJALV9BQ7;
INFOPLIST_FILE = "App/Supporting Files/Info.plist"; INFOPLIST_FILE = "App/Supporting Files/Info.plist";
INFOPLIST_KEY_CFBundleDisplayName = Attractor;
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities";
IPHONEOS_DEPLOYMENT_TARGET = 18.0;
LD_RUNPATH_SEARCH_PATHS = ( LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)", "$(inherited)",
"@executable_path/Frameworks", "@executable_path/Frameworks",
); );
MARKETING_VERSION = 4.0; MARKETING_VERSION = 4.1;
PRODUCT_BUNDLE_IDENTIFIER = net.buzzert.attractor; PRODUCT_BUNDLE_IDENTIFIER = net.buzzert.attractor;
PRODUCT_NAME = Attractor; PRODUCT_NAME = Attractor;
PROVISIONING_PROFILE_SPECIFIER = "";
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
SUPPORTS_MACCATALYST = YES; SUPPORTS_MACCATALYST = YES;
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO;
SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO;
SWIFT_OBJC_BRIDGING_HEADER = "App/Supporting Files/SBrowser-Bridging-Header.h"; SWIFT_OBJC_BRIDGING_HEADER = "App/Supporting Files/SBrowser-Bridging-Header.h";
SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0; SWIFT_VERSION = 5.0;
@@ -857,58 +823,33 @@
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = "App/Supporting Files/SBrowser.entitlements"; CODE_SIGN_ENTITLEMENTS = "App/Supporting Files/SBrowser.entitlements";
CODE_SIGN_IDENTITY = "-"; CODE_SIGN_IDENTITY = "Apple Distribution";
CURRENT_PROJECT_VERSION = 4; CODE_SIGN_STYLE = Manual;
CURRENT_PROJECT_VERSION = 6;
DEAD_CODE_STRIPPING = YES; DEAD_CODE_STRIPPING = YES;
DEVELOPMENT_TEAM = DQQH5H6GBD;
INFOPLIST_FILE = "App/Supporting Files/Info.plist"; INFOPLIST_FILE = "App/Supporting Files/Info.plist";
INFOPLIST_KEY_CFBundleDisplayName = Attractor;
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities";
IPHONEOS_DEPLOYMENT_TARGET = 18.0;
LD_RUNPATH_SEARCH_PATHS = ( LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)", "$(inherited)",
"@executable_path/Frameworks", "@executable_path/Frameworks",
); );
MARKETING_VERSION = 4.0; MARKETING_VERSION = 4.1;
PRODUCT_BUNDLE_IDENTIFIER = net.buzzert.attractor; PRODUCT_BUNDLE_IDENTIFIER = net.buzzert.attractor;
PRODUCT_NAME = Attractor; PRODUCT_NAME = Attractor;
PROVISIONING_PROFILE_SPECIFIER = "Attractor AppStore CI";
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
SUPPORTS_MACCATALYST = YES; SUPPORTS_MACCATALYST = YES;
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO;
SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO;
SWIFT_OBJC_BRIDGING_HEADER = "App/Supporting Files/SBrowser-Bridging-Header.h"; SWIFT_OBJC_BRIDGING_HEADER = "App/Supporting Files/SBrowser-Bridging-Header.h";
SWIFT_VERSION = 5.0; SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2,6"; TARGETED_DEVICE_FAMILY = "1,2,6";
}; };
name = Release; name = Release;
}; };
1ADFF48824C8C12F006DC7AE /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
DEAD_CODE_STRIPPING = YES;
INFOPLIST_FILE = SBrowserProcessBundle/Info.plist;
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Bundles";
PRODUCT_BUNDLE_IDENTIFIER = net.buzzert.SBrowserProcessBundle;
PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = iphoneos.internal;
STRIP_INSTALLED_PRODUCT = NO;
SUPPORTS_MACCATALYST = YES;
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO;
TARGETED_DEVICE_FAMILY = "1,2,6";
WRAPPER_EXTENSION = bundle;
};
name = Debug;
};
1ADFF48924C8C12F006DC7AE /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
DEAD_CODE_STRIPPING = YES;
INFOPLIST_FILE = SBrowserProcessBundle/Info.plist;
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Bundles";
PRODUCT_BUNDLE_IDENTIFIER = net.buzzert.SBrowserProcessBundle;
PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = iphoneos.internal;
SUPPORTS_MACCATALYST = YES;
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO;
TARGETED_DEVICE_FAMILY = "1,2,6";
WRAPPER_EXTENSION = bundle;
};
name = Release;
};
/* End XCBuildConfiguration section */ /* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */ /* Begin XCConfigurationList section */
@@ -930,15 +871,6 @@
defaultConfigurationIsVisible = 0; defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release; defaultConfigurationName = Release;
}; };
1ADFF48724C8C12F006DC7AE /* Build configuration list for PBXNativeTarget "SBrowserProcessBundle" */ = {
isa = XCConfigurationList;
buildConfigurations = (
1ADFF48824C8C12F006DC7AE /* Debug */,
1ADFF48924C8C12F006DC7AE /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */ /* End XCConfigurationList section */
/* Begin XCVersionGroup section */ /* Begin XCVersionGroup section */
@@ -1,10 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<Scheme <Scheme
LastUpgradeVersion = "1400" LastUpgradeVersion = "1600"
version = "1.7"> version = "1.7">
<BuildAction <BuildAction
parallelizeBuildables = "YES" parallelizeBuildables = "YES"
buildImplicitDependencies = "YES"> buildImplicitDependencies = "YES"
buildArchitectures = "Automatic">
<BuildActionEntries> <BuildActionEntries>
<BuildActionEntry <BuildActionEntry
buildForTesting = "YES" buildForTesting = "YES"
@@ -26,9 +27,8 @@
buildConfiguration = "Debug" buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES"> shouldUseLaunchSchemeArgsEnv = "YES"
<Testables> shouldAutocreateTestPlan = "YES">
</Testables>
</TestAction> </TestAction>
<LaunchAction <LaunchAction
buildConfiguration = "Debug" buildConfiguration = "Debug"
@@ -39,8 +39,7 @@
ignoresPersistentStateOnLaunch = "NO" ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES" debugDocumentVersioning = "YES"
debugServiceExtension = "internal" debugServiceExtension = "internal"
allowLocationSimulation = "YES" allowLocationSimulation = "YES">
internalIOSLaunchStyle = "2">
<BuildableProductRunnable <BuildableProductRunnable
runnableDebuggingMode = "0"> runnableDebuggingMode = "0">
<BuildableReference <BuildableReference
@@ -51,13 +50,6 @@
ReferencedContainer = "container:SBrowser.xcodeproj"> ReferencedContainer = "container:SBrowser.xcodeproj">
</BuildableReference> </BuildableReference>
</BuildableProductRunnable> </BuildableProductRunnable>
<AdditionalOptions>
<AdditionalOption
key = "MallocScribble"
value = ""
isEnabled = "YES">
</AdditionalOption>
</AdditionalOptions>
</LaunchAction> </LaunchAction>
<ProfileAction <ProfileAction
buildConfiguration = "Release" buildConfiguration = "Release"
@@ -83,7 +75,4 @@
buildConfiguration = "Release" buildConfiguration = "Release"
revealArchiveInOrganizer = "YES"> revealArchiveInOrganizer = "YES">
</ArchiveAction> </ArchiveAction>
<InstallAction
buildConfiguration = "Release">
</InstallAction>
</Scheme> </Scheme>
-24
View File
@@ -1,24 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleVersion</key>
<string>1</string>
<key>NSPrincipalClass</key>
<string>SBRProcessPlugin</string>
</dict>
</plist>
-17
View File
@@ -1,17 +0,0 @@
//
// SBRProcessPlugin.h
// SBrowserProcessBundle
//
// Created by James Magahern on 7/22/20.
//
#import <Foundation/Foundation.h>
#import <WebKit/WKWebProcessPlugIn.h>
NS_ASSUME_NONNULL_BEGIN
@interface SBRProcessPlugin : NSObject <WKWebProcessPlugIn>
@end
NS_ASSUME_NONNULL_END
-115
View File
@@ -1,115 +0,0 @@
//
// SBRProcessPlugin.m
// SBrowserProcessBundle
//
// Created by James Magahern on 7/22/20.
//
#import "SBRProcessPlugin.h"
#import "SBRWebProcessDelegate.h"
#import "SBRWebProcessProxy.h"
#import "SBRScriptPolicy.h"
#import <WebKit/_WKRemoteObjectInterface.h>
#import <WebKit/_WKRemoteObjectRegistry.h>
#import <WebKit/WKWebProcessPlugInBrowserContextController.h>
#import <WebKit/WKWebProcessPlugInBrowserContextControllerPrivate.h>
#import <WebKit/WKWebProcessPlugInFrame.h>
#import <WebKit/WKWebProcessPlugInLoadDelegate.h>
@interface SBRProcessPlugin () <WKWebProcessPlugInLoadDelegate, SBRWebProcessProxy>
@property (nonatomic, strong) id<SBRWebProcessDelegate> processDelegate;
@property (nonatomic, assign) BOOL allScriptsAllowed;
@property (nonatomic, strong) NSMutableSet<NSString *> *allowedResourceOrigins;
@property (nonatomic, strong) NSDictionary<NSString *, NSNumber *> *policyTypeByOrigin;
@end
@implementation SBRProcessPlugin
+ (void)initialize
{
[super initialize];
NSLog(@"SBRProcessPlugin Initialized");
}
#pragma mark <SBRWebProcessProxy>
- (void)hello
{
NSLog(@"SBRProcessPlugin: Helloooooo");
}
- (void)syncAllowedResourceOrigins:(NSArray<NSString *> *)allowedOrigins
{
_allowedResourceOrigins = [NSMutableSet setWithArray:allowedOrigins];
}
- (void)syncPolicyTypes:(NSDictionary<NSString *,NSNumber *> *)policyTypes
{
_policyTypeByOrigin = policyTypes;
}
- (void)setAllScriptsAllowed:(BOOL)allScriptsAllowed
{
_allScriptsAllowed = allScriptsAllowed;
}
#pragma mark <WKWebProcessPlugIn>
- (void)webProcessPlugIn:(WKWebProcessPlugInController *)plugInController initializeWithObject:(id)initializationObject
{
_allowedResourceOrigins = [[plugInController parameters] valueForKey:SBRGetAllowedOriginsKey()];
_allScriptsAllowed = [[[plugInController parameters] valueForKey:SBRGetAllScriptsAllowedKey()] boolValue];
_policyTypeByOrigin = [[plugInController parameters] valueForKey:SBRGetPolicyTypeByOriginKey()];
NSLog(@"SBRProcessPlugin: %lu origins allowed, all scripts allowed: %@ ", (unsigned long)_allowedResourceOrigins.count, _allScriptsAllowed ? @"YES" : @"NO");
}
- (void)webProcessPlugIn:(WKWebProcessPlugInController *)plugInController didCreateBrowserContextController:(WKWebProcessPlugInBrowserContextController *)browserContextController
{
_WKRemoteObjectInterface *proxyInterface = [_WKRemoteObjectInterface remoteObjectInterfaceWithProtocol:@protocol(SBRWebProcessProxy)];
[browserContextController._remoteObjectRegistry registerExportedObject:self interface:proxyInterface];
_WKRemoteObjectInterface *eventListenerInterface = [_WKRemoteObjectInterface remoteObjectInterfaceWithProtocol:@protocol(SBRWebProcessDelegate)];
self.processDelegate = [browserContextController._remoteObjectRegistry remoteObjectProxyWithInterface:eventListenerInterface];
browserContextController.loadDelegate = self;
[[self processDelegate] webProcessDidConnect];
}
#pragma mark <WKWebProcessPlugInLoadDelegate>
- (NSURLRequest *)webProcessPlugInBrowserContextController:(WKWebProcessPlugInBrowserContextController *)controller frame:(WKWebProcessPlugInFrame *)frame willSendRequest:(NSURLRequest *)request redirectResponse:(NSURLResponse *)redirectResponse
{
NSLog(@"SBRProcessPlugin: Sending request: %@", request);
if (_allowedResourceOrigins == nil) {
NSLog(@"Allowed resource origins should not be nil!!!!");
}
NSURL *requestURL = [request URL];
NSString *resourceOrigin = [requestURL host];
NSString *requestExtension = [requestURL pathExtension];
NSString *hostOrigin = [[[controller mainFrame] URL] host];
if (requestExtension.length > 0 && [requestExtension isEqualToString:@"js"]) {
NSNumber *policyType = [_policyTypeByOrigin objectForKey:hostOrigin];
NSLog(@"SBRProcessPlugin: Policy type for %@: %@", hostOrigin, policyType);
SBRScriptPolicy *policy = [[SBRScriptPolicy alloc] initWithSecurityOrigin:hostOrigin policyType:[policyType integerValue]];
if ([self allScriptsAllowed] || [policy allowsExternalJavaScriptResourceOrigin:resourceOrigin]) {
NSLog(@"SBRProcessPlugin: Policy allows script requestURL: %@", requestURL);
[[self processDelegate] webProcessDidAllowScriptWithOrigin:resourceOrigin];
} else {
NSLog(@"SBRProcessPlugin: Policy disallows script requestURL: %@", requestURL);
[[self processDelegate] webProcessDidBlockScriptWithOrigin:resourceOrigin];
request = nil;
}
}
return request;
}
@end
@@ -1,16 +0,0 @@
//
// SBRWebProcessDelegate.h
// SBrowser
//
// Created by James Magahern on 7/22/20.
//
#import <Foundation/Foundation.h>
@protocol SBRWebProcessDelegate <NSObject>
- (void)webProcessDidConnect;
- (void)webProcessDidBlockScriptWithOrigin:(NSString *)origin;
- (void)webProcessDidAllowScriptWithOrigin:(NSString *)origin;
@end
@@ -1,29 +0,0 @@
//
// SBRWebProcessProxy.h
// SBrowser
//
// Created by James Magahern on 7/22/20.
//
#import <Foundation/Foundation.h>
static inline NSString* SBRGetAllowedOriginsKey() {
return @"allowedOrigins";
}
static inline NSString* SBRGetAllScriptsAllowedKey() {
return @"allScriptsAllowed";
}
static inline NSString* SBRGetPolicyTypeByOriginKey() {
return @"policyTypeByOrigin";
}
@protocol SBRWebProcessProxy <NSObject>
- (void)hello;
- (void)setAllScriptsAllowed:(BOOL)allowed;
- (void)syncAllowedResourceOrigins:(NSArray<NSString *> *)allowedOrigins;
- (void)syncPolicyTypes:(NSDictionary<NSString *, NSNumber *> *)policyTypes;
@end
Binary file not shown.

After

Width:  |  Height:  |  Size: 442 KiB

+162
View File
@@ -0,0 +1,162 @@
/*
* Copyright (C) 2010-2017 Apple Inc. All rights reserved.
* Portions Copyright (c) 2010 Motorola Mobility, 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.
*/
#ifndef WKBase_h
#define WKBase_h
#include <WebKit/WKDeclarationSpecifiers.h>
#include <stdint.h>
#include <WebKit/WKBaseMac.h>
/* WebKit2 shared types */
typedef uint32_t WKTypeID;
typedef const void* WKTypeRef;
typedef const struct OpaqueWKArray* WKArrayRef;
typedef struct OpaqueWKArray* WKMutableArrayRef;
typedef const struct OpaqueWKDictionary* WKDictionaryRef;
typedef struct OpaqueWKDictionary* WKMutableDictionaryRef;
typedef const struct OpaqueWKBoolean* WKBooleanRef;
typedef const struct OpaqueWKCertificateInfo* WKCertificateInfoRef;
typedef const struct OpaqueWKConnection* WKConnectionRef;
typedef const struct OpaqueWKContextMenuItem* WKContextMenuItemRef;
typedef const struct OpaqueWKData* WKDataRef;
typedef const struct OpaqueWKDouble* WKDoubleRef;
typedef const struct OpaqueWKError* WKErrorRef;
typedef const struct OpaqueWKGraphicsContext* WKGraphicsContextRef;
typedef const struct OpaqueWKImage* WKImageRef;
typedef const struct OpaqueWKPointRef* WKPointRef;
typedef const struct OpaqueWKRectRef* WKRectRef;
typedef const struct OpaqueWKSecurityOrigin* WKSecurityOriginRef;
typedef const struct OpaqueWKSerializedScriptValue* WKSerializedScriptValueRef;
typedef const struct OpaqueWKSizeRef* WKSizeRef;
typedef const struct OpaqueWKString* WKStringRef;
typedef const struct OpaqueWKUInt64* WKUInt64Ref;
typedef const struct OpaqueWKURL* WKURLRef;
typedef const struct OpaqueWKURLRequest* WKURLRequestRef;
typedef const struct OpaqueWKURLResponse* WKURLResponseRef;
typedef const struct OpaqueWKUserContentURLPattern* WKUserContentURLPatternRef;
typedef const struct OpaqueWKWebArchive* WKWebArchiveRef;
typedef const struct OpaqueWKWebArchiveResource* WKWebArchiveResourceRef;
/* WebKit2 main API types */
typedef const struct OpaqueWKApplicationCacheManager* WKApplicationCacheManagerRef;
typedef const struct OpaqueWKAuthenticationChallenge* WKAuthenticationChallengeRef;
typedef const struct OpaqueWKAuthenticationDecisionListener* WKAuthenticationDecisionListenerRef;
typedef const struct OpaqueWKBackForwardList* WKBackForwardListRef;
typedef const struct OpaqueWKBackForwardListItem* WKBackForwardListItemRef;
typedef const struct OpaqueWKResourceCacheManager* WKResourceCacheManagerRef;
typedef const struct OpaqueWKColorPickerResultListener* WKColorPickerResultListenerRef;
typedef const struct OpaqueWKContext* WKContextRef;
typedef const struct OpaqueWKContextConfiguration* WKContextConfigurationRef;
typedef const struct OpaqueWKContextMenuListener* WKContextMenuListenerRef;
typedef const struct OpaqueWKCookieManager* WKCookieManagerRef;
typedef const struct OpaqueWKCredential* WKCredentialRef;
typedef const struct OpaqueWKDownload* WKDownloadRef;
typedef const struct OpaqueWKExperimentalFeature* WKFeatureRef;
typedef const struct OpaqueWKFormSubmissionListener* WKFormSubmissionListenerRef;
typedef const struct OpaqueWKFrameHandle* WKFrameHandleRef;
typedef const struct OpaqueWKFrameInfo* WKFrameInfoRef;
typedef const struct OpaqueWKFrame* WKFrameRef;
typedef const struct OpaqueWKFramePolicyListener* WKFramePolicyListenerRef;
typedef const struct OpaqueWKGeolocationManager* WKGeolocationManagerRef;
typedef const struct OpaqueWKGeolocationPermissionRequest* WKGeolocationPermissionRequestRef;
typedef const struct OpaqueWKGeolocationPosition* WKGeolocationPositionRef;
typedef const struct OpaqueWKGrammarDetail* WKGrammarDetailRef;
typedef const struct OpaqueWKHTTPCookieStore* WKHTTPCookieStoreRef;
typedef const struct OpaqueWKHitTestResult* WKHitTestResultRef;
typedef const struct OpaqueWKIconDatabase* WKIconDatabaseRef;
typedef const struct OpaqueWKInspector* WKInspectorRef;
typedef const struct OpaqueWKInternalDebugFeature* WKInternalDebugFeatureRef;
typedef const struct OpaqueWKKeyValueStorageManager* WKKeyValueStorageManagerRef;
typedef const struct OpaqueWKMessageListener* WKMessageListenerRef;
typedef const struct OpaqueWKNavigationAction* WKNavigationActionRef;
typedef const struct OpaqueWKNavigationData* WKNavigationDataRef;
typedef const struct OpaqueWKNavigation* WKNavigationRef;
typedef const struct OpaqueWKNavigationResponse* WKNavigationResponseRef;
typedef const struct OpaqueWKNotification* WKNotificationRef;
typedef const struct OpaqueWKNotificationManager* WKNotificationManagerRef;
typedef const struct OpaqueWKNotificationPermissionRequest* WKNotificationPermissionRequestRef;
typedef const struct OpaqueWKNotificationProvider* WKNotificationProviderRef;
typedef const struct OpaqueWKOpenPanelParameters* WKOpenPanelParametersRef;
typedef const struct OpaqueWKOpenPanelResultListener* WKOpenPanelResultListenerRef;
typedef const struct OpaqueWKPage* WKPageRef;
typedef const struct OpaqueWKPageConfiguration* WKPageConfigurationRef;
typedef const struct OpaqueWKPageGroup* WKPageGroupRef;
typedef const struct OpaqueWKPreferences* WKPreferencesRef;
typedef const struct OpaqueWKProtectionSpace* WKProtectionSpaceRef;
typedef const struct OpaqueWKPageRunBeforeUnloadConfirmPanelResultListener* WKPageRunBeforeUnloadConfirmPanelResultListenerRef;
typedef const struct OpaqueWKPageRunJavaScriptAlertResultListener* WKPageRunJavaScriptAlertResultListenerRef;
typedef const struct OpaqueWKPageRunJavaScriptConfirmResultListener* WKPageRunJavaScriptConfirmResultListenerRef;
typedef const struct OpaqueWKPageRunJavaScriptPromptResultListener* WKPageRunJavaScriptPromptResultListenerRef;
typedef const struct OpaqueWKPageRequestStorageAccessConfirmResultListener* WKPageRequestStorageAccessConfirmResultListenerRef;
typedef const struct OpaqueWKResourceLoadStatisticsManager* WKResourceLoadStatisticsManagerRef;
typedef const struct OpaqueWKTextChecker* WKTextCheckerRef;
typedef const struct OpaqueWKSession* WKSessionRef;
typedef const struct OpaqueWKSessionState* WKSessionStateRef;
typedef const struct OpaqueWKUserContentController* WKUserContentControllerRef;
typedef const struct OpaqueWKUserContentExtensionStore* WKUserContentExtensionStoreRef;
typedef const struct OpaqueWKUserContentFilter* WKUserContentFilterRef;
typedef const struct OpaqueWKUserMediaPermissionCheck* WKUserMediaPermissionCheckRef;
typedef const struct OpaqueWKUserMediaPermissionRequest* WKUserMediaPermissionRequestRef;
typedef const struct OpaqueWKUserScript* WKUserScriptRef;
typedef const struct OpaqueWKViewportAttributes* WKViewportAttributesRef;
typedef const struct OpaqueWKWebsiteDataConfigurationStore* WKWebsiteDataStoreConfigurationRef;
typedef const struct OpaqueWKWebsiteDataStore* WKWebsiteDataStoreRef;
typedef const struct OpaqueWKWebsitePolicies* WKWebsitePoliciesRef;
typedef const struct OpaqueWKWindowFeatures* WKWindowFeaturesRef;
typedef const struct OpaqueWKSpeechRecognitionPermissionCallback* WKSpeechRecognitionPermissionCallbackRef;
typedef const struct OpaqueWKMediaKeySystemPermissionRequest* WKMediaKeySystemPermissionRequestRef;
typedef const struct OpaqueWKMediaKeySystemPermissionCallback* WKMediaKeySystemPermissionCallbackRef;
typedef const struct OpaqueWKQueryPermissionResultCallback* WKQueryPermissionResultCallbackRef;
/* WebKit2 Bundle types */
typedef const struct OpaqueWKBundle* WKBundleRef;
typedef const struct OpaqueWKBundleBackForwardList* WKBundleBackForwardListRef;
typedef const struct OpaqueWKBundleBackForwardListItem* WKBundleBackForwardListItemRef;
typedef const struct OpaqueWKBundleDOMCSSStyleDeclaration* WKBundleCSSStyleDeclarationRef;
typedef const struct OpaqueWKBundleDOMWindowExtension* WKBundleDOMWindowExtensionRef;
typedef const struct OpaqueWKBundleFileHandle* WKBundleFileHandleRef;
typedef const struct OpaqueWKBundleFrame* WKBundleFrameRef;
typedef const struct OpaqueWKBundleHitTestResult* WKBundleHitTestResultRef;
typedef const struct OpaqueWKBundleInspector* WKBundleInspectorRef;
typedef const struct OpaqueWKBundleNavigationAction* WKBundleNavigationActionRef;
typedef const struct OpaqueWKBundleNodeHandle* WKBundleNodeHandleRef;
typedef const struct OpaqueWKBundlePage* WKBundlePageRef;
typedef const struct OpaqueWKBundlePageBanner* WKBundlePageBannerRef;
typedef const struct OpaqueWKBundlePageGroup* WKBundlePageGroupRef;
typedef const struct OpaqueWKBundlePageOverlay* WKBundlePageOverlayRef;
typedef const struct OpaqueWKBundleRangeHandle* WKBundleRangeHandleRef;
typedef const struct OpaqueWKBundleScriptWorld* WKBundleScriptWorldRef;
#endif /* WKBase_h */
+35
View File
@@ -0,0 +1,35 @@
/*
* Copyright (C) 2013 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.
*/
#ifndef WKBaseMac_h
#define WKBaseMac_h
#ifndef WKBase_h
#error "Please #include \"WKBase.h\" instead of this file directly."
#endif
typedef const struct OpaqueWKObjCTypeWrapper* WKObjCTypeWrapperRef;
#endif /* WKBaseMac_h */
@@ -0,0 +1,79 @@
/*
* Copyright (C) 2010, 2011 Apple Inc. All rights reserved.
* Portions Copyright (c) 2010 Motorola Mobility, 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.
*/
#ifndef WKDeclarationSpecifiers_h
#define WKDeclarationSpecifiers_h
#ifndef __has_declspec_attribute
#define __has_declspec_attribute(x) 0
#endif
#undef WK_EXPORT
#if defined(WK_NO_EXPORT)
#define WK_EXPORT
#elif defined(WIN32) || defined(_WIN32) || defined(__CC_ARM) || defined(__ARMCC__) || (__has_declspec_attribute(dllimport) && __has_declspec_attribute(dllexport))
#if defined(BUILDING_WebKit)
#define WK_EXPORT __declspec(dllexport)
#else
#define WK_EXPORT __declspec(dllimport)
#endif /* defined(BUILDING_WebKit) */
#elif defined(__GNUC__)
#define WK_EXPORT __attribute__((visibility("default")))
#else /* !defined(WK_NO_EXPORT) */
#define WK_EXPORT
#endif /* defined(WK_NO_EXPORT) */
#if !defined(WK_INLINE)
#if defined(__cplusplus)
#define WK_INLINE static inline
#elif defined(__GNUC__)
#define WK_INLINE static __inline__
#else
#define WK_INLINE static
#endif
#endif /* !defined(WK_INLINE) */
#ifndef __has_extension
#define __has_extension(x) 0
#endif
#if __has_extension(enumerator_attributes) && __has_extension(attribute_unavailable_with_message)
#define WK_C_DEPRECATED(message) __attribute__((deprecated(message)))
#else
#define WK_C_DEPRECATED(message)
#endif
#ifndef __has_attribute
#define __has_attribute(x) 0
#endif
#if __has_attribute(unavailable)
#define WK_UNAVAILABLE __attribute__((unavailable))
#else
#define WK_UNAVAILABLE
#endif
#endif /* WKDeclarationSpecifiers_h */
+47
View File
@@ -0,0 +1,47 @@
/*
* Copyright (C) 2015 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.
*/
#ifndef WKLayoutMode_h
#define WKLayoutMode_h
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
enum {
kWKLayoutModeViewSize = 0,
kWKLayoutModeFixedSize = 1,
kWKLayoutModeDynamicSizeComputedFromViewScale = 2,
kWKLayoutModeDynamicSizeComputedFromMinimumDocumentSize = 4
};
typedef uint32_t WKLayoutMode;
#ifdef __cplusplus
}
#endif
#endif /* WKLayoutMode_h */
@@ -0,0 +1,149 @@
/*
* Copyright (C) 2014-2022 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/WKBase.h>
#import <WebKit/WKProcessPool.h>
#import <WebKit/WKSecurityOrigin.h>
@class WKWebView;
@class WKWebsiteDataStore;
@class _WKAutomationSession;
@class _WKDownload;
@class _WKProcessPoolConfiguration;
@protocol _WKAutomationDelegate;
@protocol _WKDownloadDelegate;
@protocol _WKGeolocationCoreLocationProvider;
@interface WKProcessPool ()
- (instancetype)_initWithConfiguration:(_WKProcessPoolConfiguration *)configuration __attribute__((objc_method_family(init))) NS_DESIGNATED_INITIALIZER;
@end
@interface WKProcessPool (WKPrivate)
+ (WKProcessPool *)_sharedProcessPool;
+ (NSArray<WKProcessPool *> *)_allProcessPoolsForTesting WK_API_AVAILABLE(macos(10.14), ios(12.0));
@property (nonatomic, readonly) _WKProcessPoolConfiguration *_configuration;
- (void)_setAllowsSpecificHTTPSCertificate:(NSArray *)certificateChain forHost:(NSString *)host WK_API_DEPRECATED_WITH_REPLACEMENT("WKWebsiteDataStore._allowTLSCertificateChain:forHost:", macos(10.10, 12.0), ios(8.0, 15.0));
- (void)_setCanHandleHTTPSServerTrustEvaluation:(BOOL)value WK_API_DEPRECATED_WITH_REPLACEMENT("_WKWebsiteDataStoreConfiguration.fastServerTrustEvaluationEnabled", macos(10.11, 10.15.4), ios(9.0, 13.4));
- (id)_objectForBundleParameter:(NSString *)parameter;
- (void)_setObject:(id <NSCopying, NSSecureCoding>)object forBundleParameter:(NSString *)parameter;
// FIXME: This should be NSDictionary<NSString *, id <NSCopying, NSSecureCoding>>
- (void)_setObjectsForBundleParametersWithDictionary:(NSDictionary *)dictionary WK_API_AVAILABLE(macos(10.12), ios(10.0));
#if !TARGET_OS_IPHONE
- (void)_resetPluginLoadClientPolicies:(NSDictionary *)policies WK_API_AVAILABLE(macos(10.13));
@property (nonatomic, readonly, copy) NSDictionary *_pluginLoadClientPolicies WK_API_AVAILABLE(macos(10.13));
#endif
@property (nonatomic, weak, setter=_setDownloadDelegate:) id <_WKDownloadDelegate> _downloadDelegate WK_API_DEPRECATED_WITH_REPLACEMENT("WKDownload.downloadDelegate", macos(10.10, 12.0), ios(8.0, 15.0));
@property (nonatomic, weak, setter=_setAutomationDelegate:) id <_WKAutomationDelegate> _automationDelegate WK_API_AVAILABLE(macos(10.12), ios(10.0));
#if TARGET_OS_IPHONE
@property (nonatomic, setter=_setCoreLocationProvider:) id <_WKGeolocationCoreLocationProvider> _coreLocationProvider WK_API_AVAILABLE(ios(11.0));
#endif
+ (NSURL *)_websiteDataURLForContainerWithURL:(NSURL *)containerURL;
+ (NSURL *)_websiteDataURLForContainerWithURL:(NSURL *)containerURL bundleIdentifierIfNotInContainer:(NSString *)bundleIdentifier;
+ (void)_setWebProcessCountLimit:(unsigned)limit WK_API_AVAILABLE(macos(13.0), ios(16.0));
- (void)_warmInitialProcess WK_API_AVAILABLE(macos(10.12), ios(10.0));
- (void)_automationCapabilitiesDidChange WK_API_AVAILABLE(macos(10.12), ios(10.0));
- (void)_setAutomationSession:(_WKAutomationSession *)automationSession WK_API_AVAILABLE(macos(10.12), ios(10.0));
@property (nonatomic, copy, setter=_setJavaScriptConfigurationDirectory:) NSURL *_javaScriptConfigurationDirectory WK_API_AVAILABLE(macos(10.15.4), ios(13.4));
- (void)_addSupportedPlugin:(NSString *) domain named:(NSString *) name withMimeTypes: (NSSet<NSString *> *) mimeTypes withExtensions: (NSSet<NSString *> *) extensions WK_API_AVAILABLE(macos(10.14), ios(12.0));
- (void)_clearSupportedPlugins WK_API_AVAILABLE(macos(10.14), ios(12.0));
- (void)_registerURLSchemeAsCanDisplayOnlyIfCanRequest:(NSString *)scheme WK_API_AVAILABLE(macos(10.14), ios(12.0));
- (void)_registerURLSchemeAsSecure:(NSString *)scheme WK_API_AVAILABLE(macos(12.0), ios(15.0));
- (void)_registerURLSchemeAsBypassingContentSecurityPolicy:(NSString *)scheme WK_API_AVAILABLE(macos(12.0), ios(15.0));
- (void)_setDomainRelaxationForbiddenForURLScheme:(NSString *)scheme WK_API_AVAILABLE(macos(12.0), ios(15.0));
- (_WKDownload *)_downloadURLRequest:(NSURLRequest *)request websiteDataStore:(WKWebsiteDataStore *)dataStore originatingWebView:(WKWebView *)webView WK_API_DEPRECATED_WITH_REPLACEMENT("WKWebView _downloadRequest", macos(10.10, 12.0), ios(8.0, 15.0));
- (_WKDownload *)_resumeDownloadFromData:(NSData *)resumeData websiteDataStore:(WKWebsiteDataStore *)dataStore path:(NSString *)path originatingWebView:(WKWebView *)webView WK_API_DEPRECATED_WITH_REPLACEMENT("WKWebView.resumeDownloadFromResumeData:completionHandler:", macos(10.10, 12.0), ios(8.0, 15.0));
+ (void)_setLinkedOnOrAfterEverything WK_API_AVAILABLE(macos(13.0), ios(16.0));
// Test only. Should be called only while no web content processes are running.
- (void)_terminateServiceWorkers WK_API_AVAILABLE(macos(10.14), ios(12.0));
// Test only.
- (pid_t)_prewarmedProcessIdentifier WK_API_AVAILABLE(macos(10.15), ios(13.0));
- (void)_terminateAllWebContentProcesses;
// Test only.
- (size_t)_webProcessCount WK_API_AVAILABLE(macos(10.13), ios(11.0));
- (BOOL)_hasPrewarmedWebProcess WK_API_AVAILABLE(macos(10.14.4), ios(12.2));
- (size_t)_webProcessCountIgnoringPrewarmed WK_API_AVAILABLE(macos(10.14), ios(12.0));
- (size_t)_webProcessCountIgnoringPrewarmedAndCached WK_API_AVAILABLE(macos(10.14.4), ios(12.2));
- (size_t)_pluginProcessCount WK_API_AVAILABLE(macos(10.13.4), ios(11.3));
- (size_t)_serviceWorkerProcessCount WK_API_AVAILABLE(macos(10.14), ios(12.0));
- (void)_makeNextWebProcessLaunchFailForTesting WK_API_AVAILABLE(macos(10.14), ios(12.0));
- (NSUInteger)_maximumSuspendedPageCount WK_API_AVAILABLE(macos(10.14.4), ios(12.2));
- (NSUInteger)_processCacheCapacity WK_API_AVAILABLE(macos(10.14.4), ios(12.2));
- (NSUInteger)_processCacheSize WK_API_AVAILABLE(macos(10.15), ios(13.0));
- (void)_clearWebProcessCache WK_API_AVAILABLE(macos(10.15.4), ios(13.4));
- (void)_setUseSeparateServiceWorkerProcess:(BOOL)forceServiceWorkerProcess WK_API_AVAILABLE(macos(10.15.4), ios(13.4));
- (pid_t)_gpuProcessIdentifier WK_API_AVAILABLE(macos(12.0), ios(15.0));
- (BOOL)_hasAudibleMediaActivity WK_API_AVAILABLE(macos(13.0), ios(16.0));
- (BOOL)_requestWebProcessTermination:(pid_t)pid WK_API_AVAILABLE(macos(12.0), ios(15.0));
// Test only. Returns web processes running web pages (does not include web processes running service workers)
- (size_t)_webPageContentProcessCount WK_API_AVAILABLE(macos(10.13.4), ios(11.3));
// Test only. Should be called before any web content processes are launched.
+ (void)_forceGameControllerFramework WK_API_AVAILABLE(macos(10.13), ios(11.0));
+ (void)_setLinkedOnOrAfterEverythingForTesting WK_API_AVAILABLE(macos(12.0), ios(15.0));
+ (void)_setLinkedOnOrBeforeEverythingForTesting WK_API_AVAILABLE(macos(12.0), ios(15.0));
+ (void)_setCaptivePortalModeEnabledGloballyForTesting:(BOOL)isEnabled WK_API_AVAILABLE(macos(13.0), ios(16.0));
+ (void)_clearCaptivePortalModeEnabledGloballyForTesting WK_API_AVAILABLE(macos(13.0), ios(16.0));
+ (BOOL)_lockdownModeEnabledGloballyForTesting WK_API_AVAILABLE(macos(13.0), ios(16.0));
- (void)_preconnectToServer:(NSURL *)serverURL WK_API_DEPRECATED_WITH_REPLACEMENT("WKWebView._preconnectToServer", macos(10.13.4, 10.15.4), ios(11.3, 13.4));
// Test only.
- (void)_getActivePagesOriginsInWebProcessForTesting:(pid_t)pid completionHandler:(void(^)(NSArray<NSString *> *))completionHandler WK_API_AVAILABLE(macos(10.14.4), ios(12.2));
- (void)_clearPermanentCredentialsForProtectionSpace:(NSURLProtectionSpace *)protectionSpace WK_API_AVAILABLE(macos(10.15), ios(13.0));
@property (nonatomic, getter=_isCookieStoragePartitioningEnabled, setter=_setCookieStoragePartitioningEnabled:) BOOL _cookieStoragePartitioningEnabled WK_API_DEPRECATED("Partitioned cookies are no longer supported", macos(10.12.3, 10.14.4), ios(10.3, 12.2));
- (WKNotificationManagerRef)_notificationManagerForTesting;
// Test only.
- (void)_seedResourceLoadStatisticsForTestingWithFirstParty:(NSURL *)firstPartyURL thirdParty:(NSURL *)thirdPartyURL shouldScheduleNotification:(BOOL)shouldScheduleNotification completionHandler:(void(^)(void))completionHandler WK_API_AVAILABLE(macos(10.15.4), ios(13.4));
- (void)_garbageCollectJavaScriptObjectsForTesting WK_API_AVAILABLE(macos(11.0), ios(14.0));
- (size_t)_numberOfConnectedGamepadsForTesting WK_API_AVAILABLE(macos(11.0), ios(14.0));
- (size_t)_numberOfConnectedHIDGamepadsForTesting WK_API_AVAILABLE(macos(11.0), ios(15.0));
- (size_t)_numberOfConnectedGameControllerFrameworkGamepadsForTesting WK_API_AVAILABLE(macos(11.0), ios(14.0));
- (void)_setUsesOnlyHIDGamepadProviderForTesting:(BOOL)usesHIDProvider WK_API_AVAILABLE(macos(11.0), ios(14.0));
@end
@@ -0,0 +1,63 @@
/*
* Copyright (C) 2015 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/WKUserContentController.h>
@class WKContentWorld;
@class WKUserScript;
@class _WKUserContentFilter;
@class _WKUserContentWorld;
@class _WKUserStyleSheet;
@interface WKUserContentController (WKPrivate)
- (void)_removeUserScript:(WKUserScript *)userScript WK_API_AVAILABLE(macos(10.12), ios(10.0));
- (void)_removeAllUserScriptsAssociatedWithContentWorld:(WKContentWorld *)contentWorld WK_API_AVAILABLE(macos(11.0), ios(14.0));
- (void)_addUserScriptImmediately:(WKUserScript *)userScript WK_API_AVAILABLE(macos(10.14), ios(12.0));
// FIXME: Remove this once rdar://100785999 is unblocked.
- (void)_addUserContentFilter:(_WKUserContentFilter *)userContentFilter WK_API_DEPRECATED_WITH_REPLACEMENT("addContentRuleList", macos(10.11, 13.3), ios(9.0, 16.4));
- (void)_removeUserContentFilter:(NSString *)userContentFilterName WK_API_AVAILABLE(macos(10.11), ios(9.0));
- (void)_removeAllUserContentFilters WK_API_AVAILABLE(macos(10.11), ios(9.0));
- (void)_addContentRuleList:(WKContentRuleList *)contentRuleList extensionBaseURL:(NSURL *)extensionBaseURL WK_API_AVAILABLE(macos(13.0), ios(16.0));
@property (nonatomic, readonly, copy) NSArray<_WKUserStyleSheet *> *_userStyleSheets WK_API_AVAILABLE(macos(10.12), ios(10.0));
- (void)_addUserStyleSheet:(_WKUserStyleSheet *)userStyleSheet WK_API_AVAILABLE(macos(10.12), ios(10.0));
- (void)_removeUserStyleSheet:(_WKUserStyleSheet *)userStyleSheet WK_API_AVAILABLE(macos(10.12), ios(10.0));
- (void)_removeAllUserStyleSheets WK_API_AVAILABLE(macos(10.12), ios(10.0));
- (void)_removeAllUserStyleSheetsAssociatedWithContentWorld:(WKContentWorld *)contentWorld WK_API_AVAILABLE(macos(11.0), ios(14.0));
- (void)_addScriptMessageHandler:(id <WKScriptMessageHandler>)scriptMessageHandler name:(NSString *)name userContentWorld:(_WKUserContentWorld *)userContentWorld WK_API_DEPRECATED_WITH_REPLACEMENT("_addScriptMessageHandler:name:contentWorld:", macos(10.11, 11.0), ios(9.0, 14.0));
- (void)_addScriptMessageHandler:(id <WKScriptMessageHandler>)scriptMessageHandler name:(NSString *)name contentWorld:(WKContentWorld *)contentWorld;
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
- (void)_removeScriptMessageHandlerForName:(NSString *)name userContentWorld:(_WKUserContentWorld *)userContentWorld;
- (void)_removeAllScriptMessageHandlersAssociatedWithUserContentWorld:(_WKUserContentWorld *)userContentWorld;
#pragma clang diagnostic pop
@end

Some files were not shown because too many files have changed in this diff Show More