Skip to content

fix(android): match the dev server by host and port - #729

Merged
dcalhoun merged 3 commits into
trunkfrom
fix/android-dev-server-origin-match
Sep 25, 2026
Merged

dcalhoun merged 3 commits into
trunkfrom
fix/android-dev-server-origin-match

Conversation

@dcalhoun

@dcalhoun dcalhoun commented Sep 24, 2026 •

Copy link
Copy Markdown
Member

What?

Match dev server requests by host and port.

Why?

Matching by host alone can cause mismatching (e.g., dev server vs local wp-env server). It also led to Android unit test failures if the GUTENBERG_EDITOR_URL property was set during a test run.

How?

Rely upon the existing originAuthority helper to match dev server requests.

Testing Instructions

Smoke test the Android editor when using the local dev server

  1. Set GUTENBERG_EDITOR_URL in your local.properties file.
  2. Launch the editor in the Android demo app and insert some text, images, etc

Verify GutenbergViewTest pass with GUTENBERG_EDITOR_URL set

  1. Set GUTENBERG_EDITOR_URL in your local.properties file.
  2. Verify running make test-android-library-unit results in a GutenbergViewTest > shouldOverrideUrlLoading passing outcome. The six HttpServerAuthenticationTests failures are unrelated and addressed in test(android): send Proxy-Authorization via OkHttp in auth tests #728.

Accessibility Testing Instructions

N/A, no user-facing changes.

Screenshots or screencast

N/A, no user-facing changes.


AI-generated details

When GUTENBERG_EDITOR_URL is set in android/local.properties, shouldOverrideUrlLoading compared only the dev server's host. Any port on that host loaded inside the WebView, unlike the asset and REST API checks, which compare host and port. It also made GutenbergViewTest's "blocks asset path URLs that drop the site's port" test fail locally whenever the dev server was on 10.0.2.2.

The check now compares origin authorities (host and port) via the existing originAuthority() helper, in a new isDevServerUrl() function with its own tests. Published builds are unaffected because GUTENBERG_EDITOR_URL is empty there.

To reproduce the old failure, add GUTENBERG_EDITOR_URL=http://10.0.2.2:5173/ to android/local.properties and run make test-android-library-unit. It passes on this branch; remove the line afterwards. The six HttpServerAuthenticationTests failures on patched JDKs are unrelated and fixed separately in #728.

🤖 Generated with Claude Code

The dev-server navigation check compared hosts only, so any port on the
dev server's host loaded in the WebView. That also failed GutenbergViewTest
when GUTENBERG_EDITOR_URL pointed at 10.0.2.2.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@github-actions github-actions Bot added the [Type] Bug An existing feature does not function as intended label Sep 24, 2026
@wpmobilebot

wpmobilebot commented Sep 24, 2026 •

Copy link
Copy Markdown

XCFramework Build

This PR's XCFramework is available for testing. Add the following to your Package.swift:

.package(url: "https://github.com/wordpress-mobile/GutenbergKit", branch: "pr-build/729")

Built from 97bbdc3

@dcalhoun
dcalhoun marked this pull request as ready for review September 24, 2026 16:48
@dcalhoun
dcalhoun requested a review from nbradbury September 24, 2026 16:48
@nbradbury

Copy link
Copy Markdown
Contributor

@dcalhoun I haven't tested this yet, but Claude had some findings:

Severity Location Issue Impact
Medium GutenbergView.kt:1406 isDevServerUrl returns true whenever originAuthority(editorUrl) is null and the URL has no authority, because the isNotEmpty() guard only rules out an empty string. If GUTENBERG_EDITOR_URL=10.0.2.2:5173 is set without a scheme and the user taps a mailto:/tel: link, null == null matches, the WebView tries to load it and shows ERR_UNKNOWN_URL_SCHEME instead of opening the link externally (dev-only; the old check had the same hole).
Medium GutenbergView.kt:1397 originAuthority() keeps the host's case as written, but Chromium lowercases hosts before they reach WebResourceRequest.url. With GUTENBERG_EDITOR_URL=http://MyMac.local:5173/, dev-server navigations never match and open in the external browser; the same mismatch affects assetAuthority and the siteApiRoot check when the site URL has uppercase letters (pre-existing, not introduced here).
Low GutenbergViewTest.kt:227 shouldOverrideUrlLoading still reads BuildConfig.GUTENBERG_EDITOR_URL directly, so the WebViewClient tests still depend on each developer's local.properties. A developer whose dev server URL shares an authority with a test URL (e.g. http://10.0.2.2:8888/) still gets failing tests, and no test proves shouldOverrideUrlLoading calls isDevServerUrl.
Low GutenbergView.kt:1406 isDevServerUrl compares authorities without the scheme, even though the KDoc describes an origin comparison. With GUTENBERG_EDITOR_URL=http://localhost/, a navigation to https://localhost/ (a different origin) is allowed inside the WebView (dev-only).
Low GutenbergView.kt:521 The dev-server authority comes from a compile-time constant but is parsed again with Uri.parse on every navigation. Every navigation repeats that parse; computing the authority once as a companion val with a null check is cheaper and also closes the null-match hole in the first row.

A schemeless GUTENBERG_EDITOR_URL has no authority, so it matched host-less
URLs like mailto: and loaded them in the WebView instead of externally.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@dcalhoun

dcalhoun commented Sep 25, 2026 •

Copy link
Copy Markdown
Member Author

Thank you, @nbradbury. Let me know if you have any concerns with the way each are addressed below. Ready for another review.

  1. null == null match: Fixed in a33754f. isDevServerUrl now returns false when the dev server URL has no host, with a test for mailto:/tel:.
  2. Host case: Real, and not new. It also affects asset loading for a site URL with an uppercase host, which would stop the editor loading at all. I suggest we handle it in a separate PR so this one stays dev-only.
  3. Tests reading BuildConfig: Leaving this one. They'd only collide if the dev server URL has the same host and port as a test URL, and fixing it means passing the editor URL into GutenbergView.
  4. Scheme: I reworded the KDoc to say "host and port" rather than "origin". A scheme mismatch on the dev server can't cause a real problem.
  5. Parsing per navigation: Skipping. Navigations are rare, and the null guard already closes the hole from item 1.

@nbradbury

Copy link
Copy Markdown
Contributor

@dcalhoun Changes look good to me and I'll approve. Claude did report some other issues, but sometimes I think it's just looking for trouble :) I'll leave them here for you to look over.

Severity Location Issue Impact
Medium GutenbergView.kt:1410 The authority match is case-sensitive, and only one side is lowercased: Chromium lowercases request.url's host, while originAuthority keeps Uri.host as it was typed. With GUTENBERG_EDITOR_URL=http://Nicks-MacBook.local:5173/ (the physical-device setup), dev-server navigations never match and open in the external browser; the same mismatch affects assetAuthority when the site URL has uppercase letters.
Medium GutenbergView.kt:1409 The port match relies on Uri.getPort() parsing cleanly, and android/build.gradle.kts doesn't trim the property, so a malformed port quietly drops back to a host-only authority that can never equal Chromium's host:port. A trailing space in GUTENBERG_EDITOR_URL=http://10.0.2.2:5173 makes getPort() return -1, so every dev-server navigation opens externally, a regression from the old host-only check that is hard to diagnose because loadUrl still works.
Low GutenbergView.kt:514 The REST API check still compares url.authority == originAuthority(configuration.siteApiRoot) without the null guard just added to the dev-server check. A host-less siteApiRoot plus a host-less navigation whose path contains /wp-json/ matches as null == null and loads in the WebView instead of opening externally (requires a misconfigured host app).
Low GutenbergView.kt:1410 The scheme isn't compared, and originAuthority strips the default port based on the editor URL's scheme, so http and https URLs on the same host look identical. With GUTENBERG_EDITOR_URL=https://dev.local/, a navigation to http://dev.local/ (a different origin) loads in the WebView as if it were the dev server.
Low GutenbergView.kt:1393 isDevServerUrl goes through the IPv6 path in originAuthority, which keeps an explicit default port, contradicting the new test's premise that a URL written with its default port still matches. GUTENBERG_EDITOR_URL=http://[::1]:80/ yields [::1]:80 while Chromium sends [::1], so the dev server never matches, and the helper doesn't document this limitation.
Low GutenbergView.kt:521 The compile-time BuildConfig.GUTENBERG_EDITOR_URL is parsed again with Uri.parse on every top-level navigation. Every shouldOverrideUrlLoading call allocates a Uri for a fixed string, when computing the authority once (like assetAuthority in start()) would be enough.

@nbradbury nbradbury left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

:shipit:

Properties keeps trailing whitespace, so a stray space after the port made
Uri drop it and the dev server check never match.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@dcalhoun

Copy link
Copy Markdown
Member Author

Fixed the trailing-space case in 97bbdc3. The rest are pre-existing or quite low impact. The host case was deferred in an earlier comment given it impacts production loading.

@dcalhoun
dcalhoun merged commit fcd9ca6 into trunk Sep 25, 2026
24 checks passed
@dcalhoun
dcalhoun deleted the fix/android-dev-server-origin-match branch September 25, 2026 14:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

[Type] Bug An existing feature does not function as intended

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants