Skip to content

Fix app hang when opening post list and loading more posts#24728

Merged
kean merged 3 commits intotrunkfrom
task/optimize-post-excerpt-performance
Aug 6, 2025
Merged

Fix app hang when opening post list and loading more posts#24728
kean merged 3 commits intotrunkfrom
task/optimize-post-excerpt-performance

Conversation

@kean
Copy link
Contributor

@kean kean commented Aug 5, 2025

Fixes a (massive) app hang when loading the Posts screen and loading more Posts. For this purposes (short details label on the list) all we gotta do is show the first paragraph. I tested it on P2 and it produced perfectly acceptable result. The new implementation is orders of magnitude faster.

@kean kean added this to the 26.1 milestone Aug 5, 2025
@kean kean requested a review from crazytonyli August 5, 2025 19:13
@dangermattic
Copy link
Collaborator

1 Warning
⚠️ This PR is assigned to the milestone 26.1. The due date for this milestone has already passed.
Please assign it to a milestone with a later deadline or check whether the release for this milestone has already been finished.

Generated by 🚫 Danger

@kean kean force-pushed the task/optimize-post-excerpt-performance branch from 7f0761a to aff8b88 Compare August 5, 2025 19:15
@kean kean enabled auto-merge August 5, 2025 19:15
@wpmobilebot
Copy link
Contributor

wpmobilebot commented Aug 5, 2025

App Icon📲 You can test the changes from this Pull Request in WordPress by scanning the QR code below to install the corresponding build.
App NameWordPress
ConfigurationRelease-Alpha
Build Number28417
VersionPR #24728
Bundle IDorg.wordpress.alpha
Commit2547c92
Installation URL4na9spuibh4o0
Automatticians: You can use our internal self-serve MC tool to give yourself access to those builds if needed.

@wpmobilebot
Copy link
Contributor

wpmobilebot commented Aug 5, 2025

App Icon📲 You can test the changes from this Pull Request in Jetpack by scanning the QR code below to install the corresponding build.
App NameJetpack
ConfigurationRelease-Alpha
Build Number28417
VersionPR #24728
Bundle IDcom.jetpack.alpha
Commit2547c92
Installation URL5vb9j19foktfg
Automatticians: You can use our internal self-serve MC tool to give yourself access to those builds if needed.

@crazytonyli
Copy link
Contributor

The app already depends on SwiftSoup. Have you tried using that? I presume it'd be faster than Regex matching.

let summary = content.strippingGutenbergContentForExcerpt()

XCTAssertEqual(summary, expectedSummary)
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The two test functions that test gallery blocks seem to be already covered by GutenbergExcerptGeneratorTests.swift. But should we keep the video press ones below?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, let's add it because the original "remove VideoPress blocks with regex" code was added a fix for an issue: wordpress-mobile/WordPress-iOS-Shared#339. Let's verify it still works:

    @Test
    func testVideoPressBlock() {
        let content = "<p>Before</p>\n<!-- wp:videopress/video {\"title\":\"demo\",\"description\":\"\",\"id\":5297,\"guid\":\"AbCDe\",\"videoRatio\":56.333333333333336,\"privacySetting\":2,\"allowDownload\":false,\"rating\":\"G\",\"isPrivate\":true,\"duration\":1673} -->\n<figure class=\"wp-block-videopress-video wp-block-jetpack-videopress jetpack-videopress-player\"><div class=\"jetpack-videopress-player__wrapper\">\nhttps://videopress.com/v/AbCDe?resizeToParent=true&amp;cover=true&amp;preloadContent=metadata&amp;useAverageColor=true\n</div></figure>\n<!-- /wp:videopress/video -->\n<p>After</p>"

        let summary = GutenbergExcerptGenerator.firstParagraph(from: content, maxLength: 150)
        #expect(summary == "Before")
    }

Here as expected, it picks the first paragraph regardless of what types of blocks the post has. It should now cover even more scenarios than before.

let range = NSRange(rawText.startIndex..., in: rawText)
let text = (regex?.stringByReplacingMatches(in: rawText, options: [], range: range, withTemplate: "") ?? rawText)
.stringByDecodingXMLCharacters()
.trimmingCharacters(in: .whitespacesAndNewlines)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You may be able to use SwiftSoup to simplify the parsing as:

let doc = SwiftSoup.parse(...)
let text = doc.select("p:first-of-type").text()

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't know we had it. It's used only in two places in GBM that will be removed soon. I'd suggest keeping the simpler ad-hoc version - it's probably going to be faster than SwiftSoup. SwiftSoup is pretty large and it would be nice to remove it.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's also used in GBK (see wordpress-mobile/GutenbergKit#148).

I did a quick search. It's added in 25.0 (see diff). The app file size was increased from 211 MB to 212 MB. The binary size impact seems to be okay.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, but it is a transient dependency and a pretty large one. It would be uncalled for if the app ends up with SwiftSoup as a dependency and it's used to find a single <p> tag for this small use-case.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are a few other places that use regex to parse HTML. I reckon we should replace them using a proper HTML parser instead.

Copy link
Contributor Author

@kean kean Aug 5, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, it would be a good idea to replace these. The code I wrote also should use a full blown HTML parser, but it would be ideal to move it to the background first and do it when parsing posts. I'm pretty sure parsing also currently happens on the main thread if I'm not mistaken. So there is plenty to optimize.

// Find first <p> tag content
guard let pStart = content.range(of: "<p", options: .caseInsensitive),
let pEnd = content.range(of: "</p>", options: .caseInsensitive, range: pStart.upperBound..<content.endIndex),
let tagEnd = content.range(of: ">", range: pStart.upperBound..<pEnd.lowerBound) else {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is searching for tagEnd needed? Can you search <p> instead of <p?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It covers the scenario like <p class="test">text</p>. It first finds the beginning <p and the looks for the closing tag. It's a slightly faster way to do it than with regex (I haven't measured it, so it may not be true). There is also probably a way to break it, but I think it's pretty unlikely and it is not mission critical.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It covers the scenario like <p class="test">text</p>.

Right, of course. 👍

@kean kean disabled auto-merge August 5, 2025 23:08
@kean kean enabled auto-merge August 5, 2025 23:19
@sonarqubecloud
Copy link

sonarqubecloud bot commented Aug 5, 2025

@kean kean added this pull request to the merge queue Aug 5, 2025
Merged via the queue into trunk with commit 65c30cd Aug 6, 2025
30 of 32 checks passed
@kean kean deleted the task/optimize-post-excerpt-performance branch August 6, 2025 00:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants