@@ -144,27 +144,19 @@ public class UserScriptManager: ObservableObject {
144144 . map { $0. userScripts }
145145 . removeDuplicates ( )
146146 . sink { [ weak self] _ in
147- Task { @ MainActor [ weak self] in
148- self ? . syncFromDataManager ( )
147+ Task { [ weak self] in
148+ await self ? . syncFromDataManager ( )
149149 }
150150 }
151151 . store ( in: & cancellables)
152152 logger. info ( " ✅ UserScriptManager data sync observer setup complete " )
153153 }
154154 }
155155
156- private func syncFromDataManager( ) {
156+ private func syncFromDataManager( ) async {
157157 let newUserScripts = dataManager. getUserScripts ( )
158158 logger. info ( " 🔄 Syncing userscripts from data manager: \( newUserScripts. count) scripts " )
159159
160- // Update content from stored files
161- var updatedScripts = newUserScripts
162- for i in 0 ..< updatedScripts. count {
163- if let content = readUserScriptContent ( updatedScripts [ i] ) {
164- updatedScripts [ i] . content = content
165- }
166- }
167-
168160 // If data manager has no scripts but we have defaults, don't sync from empty data manager
169161 if newUserScripts. isEmpty && !userScripts. isEmpty {
170162 logger. info (
@@ -173,13 +165,49 @@ public class UserScriptManager: ObservableObject {
173165 return
174166 }
175167
168+ // Update content from stored files (do file I/O off main thread)
169+ let scriptsToLoad = newUserScripts
170+ let updatedScripts = await Task . detached { [ weak self] ( ) -> [ UserScript ] in
171+ guard let self = self else { return scriptsToLoad }
172+ var scripts = scriptsToLoad
173+ for i in 0 ..< scripts. count {
174+ if let content = await self . readUserScriptContentOffMain ( scripts [ i] ) {
175+ scripts [ i] . content = content
176+ }
177+ }
178+ return scripts
179+ } . value
180+
176181 // Only update if the scripts have actually changed to avoid unnecessary UI updates
177182 if !areUserScriptsEqual( userScripts, updatedScripts) {
178183 userScripts = updatedScripts
179184 logger. info ( " ✅ Updated userscripts from data manager " )
180185 }
181186 }
182187
188+ /// Read userscript content off the main thread
189+ nonisolated private func readUserScriptContentOffMain( _ userScript: UserScript ) -> String ? {
190+ // Try fallback directory first
191+ if let fallbackURL = FileManager . default. urls ( for: . applicationSupportDirectory, in: . userDomainMask) . first?
192+ . appendingPathComponent ( " wBlock " ) . appendingPathComponent ( " userscripts " ) {
193+ let fileURL = fallbackURL. appendingPathComponent ( " \( userScript. id. uuidString) .user.js " )
194+ if FileManager . default. fileExists ( atPath: fileURL. path) ,
195+ let content = try ? String ( contentsOf: fileURL, encoding: . utf8) {
196+ return content
197+ }
198+ }
199+ // Then try group directory
200+ if let groupURL = FileManager . default. containerURL ( forSecurityApplicationGroupIdentifier: " group.skula.wBlock " ) ?
201+ . appendingPathComponent ( " userscripts " ) {
202+ let fileURL = groupURL. appendingPathComponent ( " \( userScript. id. uuidString) .user.js " )
203+ if FileManager . default. fileExists ( atPath: fileURL. path) ,
204+ let content = try ? String ( contentsOf: fileURL, encoding: . utf8) {
205+ return content
206+ }
207+ }
208+ return nil
209+ }
210+
183211 private func areUserScriptsEqual( _ scripts1: [ UserScript ] , _ scripts2: [ UserScript ] ) -> Bool {
184212 guard scripts1. count == scripts2. count else { return false }
185213
0 commit comments