diff --git a/Modules/Package.resolved b/Modules/Package.resolved index 4e572da38b64..378107f66551 100644 --- a/Modules/Package.resolved +++ b/Modules/Package.resolved @@ -1,5 +1,5 @@ { - "originHash" : "024ca0929c05dc22af0ce33abb347be2db737ddaa09348ee3a09c1181b56a628", + "originHash" : "dc6a09055cd83c98ea9491f1049daa2890dfd7ada78f143c12a9e2274d443cdb", "pins" : [ { "identity" : "alamofire", @@ -149,8 +149,7 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/wordpress-mobile/GutenbergKit", "state" : { - "revision" : "8addad7fd018985dd3f8b15cfcc0d028cdc189b3", - "version" : "0.13.0" + "revision" : "6c3a438dc9a6e5dbe118810d9b98052086261a0b" } }, { diff --git a/Modules/Package.swift b/Modules/Package.swift index 938b41a641f6..38cd859e50c0 100644 --- a/Modules/Package.swift +++ b/Modules/Package.swift @@ -56,7 +56,7 @@ let package = Package( .package(url: "https://github.com/wordpress-mobile/wpxmlrpc", from: "0.9.0"), .package(url: "https://github.com/wordpress-mobile/NSURL-IDN", revision: "b34794c9a3f32312e1593d4a3d120572afa0d010"), .package(url: "https://github.com/zendesk/support_sdk_ios", from: "8.0.3"), - .package(url: "https://github.com/wordpress-mobile/GutenbergKit", from: "0.13.0"), + .package(url: "https://github.com/wordpress-mobile/GutenbergKit", revision: "6c3a438dc9a6e5dbe118810d9b98052086261a0b"), // We can't use wordpress-rs branches nor commits here. Only tags work. .package(url: "https://github.com/Automattic/wordpress-rs", revision: "alpha-20260114"), .package( diff --git a/WordPress/Classes/Utility/Editor/EditorDependencyManager.swift b/WordPress/Classes/Utility/Editor/EditorDependencyManager.swift index 21b3c2616814..9734b3bc0f18 100644 --- a/WordPress/Classes/Utility/Editor/EditorDependencyManager.swift +++ b/WordPress/Classes/Utility/Editor/EditorDependencyManager.swift @@ -29,6 +29,11 @@ final class EditorDependencyManager: Sendable { /// Cached dependencies keyed by blog's ObjectID string representation. private let cache = LockingHashMap() + /// Tracks the `newGutenbergPlugins` flag value at the time the cache was last populated. + /// Used to detect when the flag changes and invalidate all stale entries. + private let pluginsFlagLock = NSLock() + private var _lastPluginsFlagValue: Bool? + /// Currently running prefetch tasks, keyed by blog's ObjectID string. private let prefetchTasks = LockingTaskHashMap() @@ -83,6 +88,16 @@ final class EditorDependencyManager: Sendable { return nil } + // Check if the plugins flag changed since we last cached + let currentPluginsFlagValue = RemoteFeatureFlag.newGutenbergPlugins.enabled() + let lastFlagValue = pluginsFlagLock.withLock { _lastPluginsFlagValue } + if let lastFlagValue, lastFlagValue != currentPluginsFlagValue { + // Flag changed - invalidate all cached entries + DDLogInfo("EditorDependencyManager: Plugins flag changed (\(lastFlagValue) -> \(currentPluginsFlagValue)), invalidating all cached dependencies") + cache.removeAll() + prefetchTasks.removeAll() + } + // Don't prefetch if we already have cached dependencies if cache[key] != nil { return nil @@ -95,6 +110,7 @@ final class EditorDependencyManager: Sendable { do { let dependencies = try await service.prepare() self.cache[key] = dependencies + self.pluginsFlagLock.withLock { self._lastPluginsFlagValue = currentPluginsFlagValue } } catch { // Prefetch failed - editor will fall back to async loading DDLogError("EditorDependencyManager: Failed to prefetch dependencies: \(error)") @@ -145,6 +161,7 @@ final class EditorDependencyManager: Sendable { /// Clears all cached dependencies. func invalidateAll() { cache.removeAll() + pluginsFlagLock.withLock { _lastPluginsFlagValue = nil } prefetchTasks.removeAll() // No need to use `removeAll` for the `invalidationTasks` } diff --git a/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/ViewModel/BlogDashboardViewModel.swift b/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/ViewModel/BlogDashboardViewModel.swift index 4a4136cbef63..00b64cfb7bea 100644 --- a/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/ViewModel/BlogDashboardViewModel.swift +++ b/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/ViewModel/BlogDashboardViewModel.swift @@ -1,6 +1,7 @@ import Foundation import UIKit import CoreData +import GutenbergKit import WordPressData import WordPressKit import WordPressCore @@ -30,6 +31,9 @@ final class BlogDashboardViewModel { private var blog: Blog + /// Tracks the last blog for which the editor was warmed up to avoid redundant warmups. + private static var lastWarmedUpBlogID: NSManagedObjectID? + private var error: Error? private let wordpressClient: WordPressClient? @@ -128,7 +132,7 @@ final class BlogDashboardViewModel { } func viewWillAppear() { - EditorDependencyManager.shared.prefetchDependencies(for: self.blog) + warmUpEditorIfNeeded(for: self.blog) quickActionsViewModel.viewWillAppear() } @@ -146,7 +150,7 @@ final class BlogDashboardViewModel { self.loadCardsFromCache() self.loadCards() - EditorDependencyManager.shared.prefetchDependencies(for: blog) + warmUpEditorIfNeeded(for: blog) } func clearEditorCache(_ completion: @escaping () -> Void) { @@ -185,6 +189,30 @@ final class BlogDashboardViewModel { private extension BlogDashboardViewModel { + /// Warms up the editor for the given blog. + /// + /// This performs two operations: + /// 1. WebKit warmup (once per blog) - pre-compiles HTML/JS + /// 2. Data prefetch (always called) - fetches settings, assets, preload list + /// + /// The prefetch is always called because `EditorDependencyManager` handles its own + /// caching and needs to detect when the plugins feature flag changes. + func warmUpEditorIfNeeded(for blog: Blog) { + guard RemoteFeatureFlag.newGutenberg.enabled() else { + return + } + + // WebKit warmup - only needed once per blog (shaves ~100-200ms) + if blog.objectID != Self.lastWarmedUpBlogID { + Self.lastWarmedUpBlogID = blog.objectID + let configuration = EditorConfiguration(blog: blog) + GutenbergKit.EditorViewController.warmup(configuration: configuration) + } + + // Data prefetch - always call to allow EditorDependencyManager to detect flag changes + EditorDependencyManager.shared.prefetchDependencies(for: blog) + } + func registerNotifications() { NotificationCenter.default.addObserver(self, selector: #selector(showDraftsCardIfNeeded), name: .newPostCreated, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(showScheduledCardIfNeeded), name: .newPostScheduled, object: nil) diff --git a/WordPress/Classes/ViewRelated/Blog/My Site/MySiteViewController.swift b/WordPress/Classes/ViewRelated/Blog/My Site/MySiteViewController.swift index 28c049b09160..0e0d32f74567 100644 --- a/WordPress/Classes/ViewRelated/Blog/My Site/MySiteViewController.swift +++ b/WordPress/Classes/ViewRelated/Blog/My Site/MySiteViewController.swift @@ -33,7 +33,6 @@ final class MySiteViewController: UIViewController, UIScrollViewDelegate, NoSite } private var currentSection: Section = .dashboard - private static var lastWarmedUpBlogID: NSManagedObjectID? @objc private(set) lazy var scrollView: UIScrollView = { @@ -341,29 +340,6 @@ final class MySiteViewController: UIViewController, UIScrollViewDelegate, NoSite configureNavBarAppearance(animated: true) } - // MARK: - Editor Warmup - - /// Warms up the editor for the given blog if it hasn't been warmed up already. - /// This avoids duplicative warmups when the site hasn't changed. - private func warmUpEditorIfNeeded(for blog: Blog) { - guard blog.objectID != Self.lastWarmedUpBlogID else { - // Editor already warmed up for this blog - return - } - - Self.lastWarmedUpBlogID = blog.objectID - - let configuration = EditorConfiguration(blog: blog) - - // 1. WebKit warmup - pre-compile HTML/JS (shaves ~100-200ms) - GutenbergKit.EditorViewController.warmup(configuration: configuration) - - // 2. Data prefetch - pre-fetch settings, assets, preload list via EditorDependencyManager - Task { - await EditorDependencyManager.shared.prefetchDependencies(for: blog) - } - } - // MARK: - Main Blog /// This VC is prepared to either show the details for a blog, or show a no-results VC configured to let the user know they have no blogs. @@ -403,9 +379,6 @@ final class MySiteViewController: UIViewController, UIScrollViewDelegate, NoSite showDashboard(for: blog) } - if RemoteFeatureFlag.newGutenberg.enabled() { - warmUpEditorIfNeeded(for: blog) - } } @objc diff --git a/WordPress/Classes/ViewRelated/Post/Controllers/PostListViewController.swift b/WordPress/Classes/ViewRelated/Post/Controllers/PostListViewController.swift index 5943da8b72fa..e06b9e2f102a 100644 --- a/WordPress/Classes/ViewRelated/Post/Controllers/PostListViewController.swift +++ b/WordPress/Classes/ViewRelated/Post/Controllers/PostListViewController.swift @@ -39,10 +39,6 @@ final class PostListViewController: AbstractPostListViewController, InteractiveP refreshNoResultsViewController = { [weak self] in self?.handleRefreshNoResultsViewController($0) } - - if let blog = self.blog { - EditorDependencyManager.shared.prefetchDependencies(for: blog) - } } private lazy var createButtonCoordinator: CreateButtonCoordinator = {