SPB Git

spb/prisme Public MIT

Navigateur iOS intelligent — chaque page comprise localement avant d'être affichée. SwiftUI · WebKit · Foundation Models, 100% on-device.

Swift 96.7% JavaScript 3.3%
2.9 KB · 82 lines swift
Raw Blame History
1//2//  WebViewPool.swift3//  Prisme4//5//  Author: Simon-Pierre Boucher <contact@spboucher.ai>6//78import Foundation9import WebKit1011/// Reusable `WKWebView` instances, partitioned by identity container.12/// Never instantiate one web view per tab (CLAUDE.md §8): tabs check a view13/// out while visible and check it back in when they leave the screen.14@MainActor15final class WebViewPool {16    private static let maxIdlePerContainer = 31718    private let containers: IdentityContainerStore19    private let rules: ContentRuleManager2021    private var idle: [IdentityContainer.ID: [WKWebView]] = [:]22    /// Weak set of every view we ever vended, so freshly compiled content23    /// rules can be applied to views already on screen.24    private let live = NSHashTable<WKWebView>.weakObjects()2526    init(containers: IdentityContainerStore, rules: ContentRuleManager) {27        self.containers = containers28        self.rules = rules29    }3031    func checkout(for containerID: IdentityContainer.ID) -> WKWebView {32        if let reused = idle[containerID]?.popLast() {33            return reused34        }35        let webView = makeWebView(for: containerID)36        live.add(webView)37        return webView38    }3940    func checkin(_ webView: WKWebView, containerID: IdentityContainer.ID) {41        webView.stopLoading()42        var stack = idle[containerID] ?? []43        guard stack.count < Self.maxIdlePerContainer else { return }44        stack.append(webView)45        idle[containerID] = stack46    }4748    /// Apply a compiled rule list to all current and future web views.49    func apply(_ ruleList: WKContentRuleList) {50        for webView in live.allObjects {51            webView.configuration.userContentController.add(ruleList)52        }53    }5455    /// Extractor source is read once; it only defines functions, so the56    /// injection cost at documentEnd is negligible (CLAUDE.md §8).57    private static let extractorSource: String? = {58        guard let url = Bundle.main.url(forResource: "extractor", withExtension: "js") else { return nil }59        return try? String(contentsOf: url, encoding: .utf8)60    }()6162    private func makeWebView(for containerID: IdentityContainer.ID) -> WKWebView {63        let configuration = WKWebViewConfiguration()64        configuration.websiteDataStore = containers.dataStore(for: containerID)65        configuration.allowsInlineMediaPlayback = true66        if let ruleList = rules.ruleList {67            configuration.userContentController.add(ruleList)68        }69        if let source = Self.extractorSource {70            configuration.userContentController.addUserScript(71                WKUserScript(source: source, injectionTime: .atDocumentEnd, forMainFrameOnly: true)72            )73        }7475        let webView = PrismeWebView(frame: .zero, configuration: configuration)76        webView.allowsBackForwardNavigationGestures = true77        webView.isFindInteractionEnabled = true78        webView.scrollView.contentInsetAdjustmentBehavior = .always79        return webView80    }81}82