Conversation
GutenbergView previously extended WebView directly and delegated all loading UI (progress bar, spinner, error states) to consumers via the EditorLoadingListener interface. This forced every app embedding the editor to implement its own loading UI boilerplate. This change makes GutenbergView extend FrameLayout instead, containing an internal WebView plus overlay views for loading states: - EditorProgressView (progress bar + label) during dependency fetching - ProgressBar (circular/indeterminate) during WebView initialization - EditorErrorView (new) for error states The view manages its own state transitions with 200ms fade animations, matching the iOS EditorViewController pattern. The EditorLoadingListener interface is removed entirely — consumers no longer need loading UI code. Changes: - GutenbergView: WebView -> FrameLayout with internal WebView child - New EditorErrorView for displaying load failures - Delete EditorLoadingListener (no longer needed) - Simplify demo EditorActivity by removing ~90 lines of loading UI - Update tests to use editorWebView accessor for WebView properties - Delete unused activity_editor.xml layout Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
… render (#317) * fix: Use dev server origin for CORS headers when configured The CORS proxy in `addCorsHeaders` was hardcoded to `https://appassets.androidplatform.net`, which is correct for bundled assets but causes CORS failures when using a local dev server via `GUTENBERG_EDITOR_URL`. The browser rejects the proxied responses because `Access-Control-Allow-Origin` doesn't match the dev server origin. Derive the origin from `GUTENBERG_EDITOR_URL` when it's set, falling back to the bundled asset origin otherwise. * fix: Ensure post id is truthy so editor becomes ready `__unstableIsEditorReady()` returns `!!state.postId`, so a post id of `0` keeps the editor permanently in a "not ready" state. Use `||` instead of `??` to fall back to `-1` for all falsy ids (not just null/undefined), matching the existing fallback path.
Logs request method, URL, headers, response status, size, and headers for every HTTP call. Network errors, HTTP errors, response bodies, and parsed WP error details are logged at the error level. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The Android RESTAPIRepository was building API URLs by simple string concatenation, ignoring siteApiNamespace. WP.com sites need a namespace like `sites/123/` inserted after the version segment in API paths (e.g., `/wp/v2/sites/123/posts`). This matches the existing iOS buildNamespacedURL logic. Also adds matching namespace URL tests to both iOS and Android. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Authorization, Cookie, and Set-Cookie header values are now replaced with <redacted> in debug logs to prevent credential leakage. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The native CORS proxy in GutenbergView now only forwards the Authorization header and cookies to known trusted hosts (site API, WP.com API, Photon CDN, and configured cached asset hosts). Requests to other domains are still proxied but without credentials, preventing token leakage if injected content triggers fetches to untrusted origins. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Use a shared OkHttpClient for proxying WebView requests, replacing the raw HttpURLConnection. OkHttp's connection pool automatically reclaims connections when the response stream is closed, eliminating the potential connection leak. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The WordPress REST API should never include sensitive information in responses, so logging the raw body on HTTP errors is acceptable and useful for debugging unexpected error formats. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This flag enables the JavaScript editor to surface network details to the native host app via the bridge. It does not control the native EditorHTTPClient's own debug-level logging. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The early return in prepareEditorSettings() skipped fetching editor settings whenever themeStyles was false, even when plugins was true. This diverged from iOS, which always delegates to the repository. The repository already has the correct guard: skip only when both plugins and themeStyles are disabled. Host apps that don't support the editor settings endpoint should set both flags to false. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…orms Ensures namespaces like "sites/123" (without trailing slash) produce the same URLs as "sites/123/" so callers don't need to worry about the convention. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Prevents withEndAction callbacks from firing on detached views if the editor is closed mid-animation. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
iOS calls the delegate for both perform and download, but Android only called it for perform. This adds EditorResponseData (matching iOS's enum) and calls the delegate after downloads complete. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replaces @unchecked Sendable with proper actor isolation so capturedURLs is synchronized. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…edFromWindow These two listeners were not being nulled out during teardown, inconsistent with all other listener cleanup in the same method. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
dcalhoun
left a comment
There was a problem hiding this comment.
Thank you for implementing this! 🙇🏻♂️
The overarching approach looks sound to me; it's aligned with much of the current iOS implementation.
While testing, I encountered a few critical issues that I noted in inline comments. We should discuss and address those.
| // it's not actually a native call – the editor is building the request so we don't | ||
| // want to modify it in any way. | ||
| Log.d(TAG, "shouldInterceptRequest: proxying request: ${request.url}") | ||
| return proxyRequest(request) |
There was a problem hiding this comment.
I encounter plugin load failures from CORS errors when launching the editor for a WoW/Atomic site or jetpack.wpmt.co.
Disabling this proxy by restate the previous implementation resolves the issue.
Example revert diff
diff --git a/android/Gutenberg/src/main/java/org/wordpress/gutenberg/GutenbergView.kt b/android/Gutenberg/src/main/java/org/wordpress/gutenberg/GutenbergView.kt
index 32c10632..78ec151d 100644
--- a/android/Gutenberg/src/main/java/org/wordpress/gutenberg/GutenbergView.kt
+++ b/android/Gutenberg/src/main/java/org/wordpress/gutenberg/GutenbergView.kt
@@ -414,6 +414,8 @@ class GutenbergView : FrameLayout {
// Cache miss – fall through to proxy below
}
+ return super.shouldInterceptRequest(view, request)
+
// Proxy all other requests natively so they bypass CORS
// (the editor page is served from appassets.androidplatform.net,
// making every API call cross-origin). This doesn't use `EditorHTTPClient` because
There appears to be something off with the new proxy logic.
There was a problem hiding this comment.
This same issue appears to cause all requests to fail with CORS errors. E.g., uploading an image fails.
| * OkHttp, which is not subject to CORS. OkHttp's connection pool handles cleanup | ||
| * automatically when the response stream is closed by the WebView. | ||
| */ | ||
| private fun proxyRequest(request: WebResourceRequest): WebResourceResponse? { |
There was a problem hiding this comment.
Presumably, this would negate the need for server logic like Automattic/jetpack#45292. Correct?
There was a problem hiding this comment.
I'd prefer to avoid requiring Jetpack for this feature to work, but WDYT?
There was a problem hiding this comment.
Yes, agreed. If we can avoid the need for Jetpack plugin code for admin-ajax support that'd be great.
However, the CORS fix is only one facet of that support. For admin-ajax support, we still must enable authenticating those requests with app passwords with the application_password_is_api_request hook we use in Automattic/jetpack#45220.
I.e., if we solve the CORS problem in the client, Jetpack is still required to enable application password authentication of admin-ajax (VideoPress only). If other plugins want that support, they need to opt in for themselves via the hook.
|
|
||
| // OkHttp requires a body for POST/PUT/PATCH even if empty | ||
| val requiresBody = method in listOf("POST", "PUT", "PATCH") | ||
| val body = if (requiresBody) "".toRequestBody(null) else null |
There was a problem hiding this comment.
Presumably, this (erroneously?) empty body is the reason image uploads fail.
From researching, it seems you cannot retrieve/pass along body values from WebResourceRequest. Is proxying mutating requests actually possible in this manner or do we need to abandon this newly added proxy?
| response.headers.forEach { (key, value) -> | ||
| responseHeaders[key] = value | ||
| } |
There was a problem hiding this comment.
Claude noted this may erroneously discard duplicate headers (e.g., multiple Set-Cookie). Should we join duplicates?
| add("public-api.wordpress.com") | ||
| // WordPress.com CDN hosts (Photon image proxy) | ||
| add("i0.wp.com") | ||
| add("i1.wp.com") | ||
| add("i2.wp.com") | ||
| add("i3.wp.com") |
There was a problem hiding this comment.
Is it feasible for us to hoist these WP.com references to host app (Wordpress and/or Demo) to keep GutenbergKit free of these references?
| } | ||
| gravity = Gravity.CENTER | ||
| TextViewCompat.setTextAppearance(this, android.R.style.TextAppearance_Material_Subhead) | ||
| text = "Failed to load editor" |
There was a problem hiding this comment.
For a future PR, we should likely localize strings like this in a manner similar to iOS.


What?
Integrate GutenbergKit with WordPress.com (WP Android) by moving editor loading UI into
GutenbergView, adding WP.com REST API namespace support, adding native CORS proxy for Android, improving HTTP logging on both platforms, and fixing falsy post ID handling.Why?
This branch prepares GutenbergKit for use in WP Android, which requires several changes:
GutenbergViewshould manage this internally, matching how iOS already works.sites/{id}/namespace segment (e.g.,/wp/v2/sites/123/posts) thatRESTAPIRepositorydidn't support.appassets.androidplatform.net, making everyfetch()to the WordPress REST API cross-origin. A native request proxy bypasses CORS without requiring server-side changes.0as a valid post ID, which caused the editor to attempt fetching a non-existent post.EditorHTTPClient.How?
Android: Move loading UI into
GutenbergView(refactor)GutenbergViewfrom extendingWebViewto extendingFrameLayout, containing an internalWebViewplus overlay views (EditorProgressView,ProgressBar,EditorErrorView).EditorLoadingListenerinterface and all consumer-side loading UI from the demo app'sEditorActivity.PROGRESS→SPINNER→READY/ERROR) are managed internally with animated transitions.Android: CORS proxy in
shouldInterceptRequestproxyRequest()toGutenbergViewthat intercepts non-asset, non-cached requests and performs them viaHttpURLConnection, bypassing CORS.CookieManagerand addsAccess-Control-Allow-*headers to responses.WP.com namespace support (iOS + Android)
buildNamespacedURL()toRESTAPIRepository(Android) and equivalent logic on iOS that inserts the namespace (e.g.,sites/123/) after the API version segment in REST paths.Post ID = 0 → nil
EditorConfigurationon both platforms now coercespostId = 0tonil.||instead of??forpost.idso falsy values (0, null, undefined) fall back to-1.Verbose HTTP logging (iOS + Android)
EditorHTTPClienton both platforms now logs full request/response details (method, URL, headers, status code, body on error, WP error codes) at debug/error levels.Other
EditorServicewhenthemeStylesis disabled.activity_editor.xmllayout file.Testing Instructions
siteApiNamespace: ["sites/{id}/"]and confirm REST API calls include the namespace.Accessibility Testing Instructions
Loading states use standard Android
ProgressBarandTextViewcomponents with system accessibility defaults. No custom accessibility handling was added — verify screen readers announce the progress bar and error state text.Screenshots or screencast
N/A – no visual design changes beyond moving existing loading UI into the library.