Skip to content

Conversation

@erseco
Copy link
Collaborator

@erseco erseco commented Nov 30, 2025

This pull request introduces support for running the Documentate converter in iframe mode, enabling compatibility with environments like WordPress Playground where popups are blocked and server headers cannot be configured. The changes add a Service Worker to enable cross-origin isolation (COOP/COEP headers) for WASM conversion in iframes, refactor the communication mechanism to use postMessage for iframe mode, and update the UI logic to select between popup and iframe modes based on the environment.

Iframe Mode & Cross-Origin Isolation Support

  • Added a Service Worker (admin/js/coi-serviceworker.js) to inject COOP/COEP headers for cross-origin isolation, allowing SharedArrayBuffer usage in iframe mode for WASM conversion.
  • Updated admin/documentate-converter-template.php to detect iframe mode, load the Service Worker synchronously, and pass iframe-specific parameters to the frontend.

Frontend Communication Refactor

  • Refactored the frontend converter logic (admin/documentate-converter-template.php) to support both popup (BroadcastChannel) and iframe (postMessage) communication, including unified result/error reporting and handling for both modes. [1] [2] [3]

UI Logic & Environment Detection

  • Updated admin/js/documentate-actions.js to detect WordPress Playground and automatically switch to iframe mode when popups are blocked, including logic for iframe creation, cleanup, and message handling. [1] [2] [3] [4]

Backend Configuration

  • Modified includes/class-documentate-admin-helper.php to detect Playground environments and set configuration flags for frontend mode selection.

General Improvements

  • Added robust cleanup, error handling, and progress reporting for both iframe and popup conversion flows. [1] [2]

These changes ensure Documentate's WASM-based conversion works reliably in both standard WordPress and restricted environments like Playground, improving compatibility and user experience.

@erseco erseco self-assigned this Nov 30, 2025
@chatgpt-codex-connector
Copy link

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, you can upgrade your account or add credits to your account and enable them for code reviews in your settings.

function isPlayground() {
const url = window.location.href;
// Check URL patterns
if (url.includes('playground.wordpress.net')) return true;

Check failure

Code scanning / CodeQL

Incomplete URL substring sanitization High

'
playground.wordpress.net
' can be anywhere in the URL, and arbitrary hosts may come before or after it.

Copilot Autofix

AI about 2 months ago

To fix the problem, the host portion of the current URL should be explicitly parsed and checked for an exact match or suitable suffix match, so that only when the page is actually loaded from playground.wordpress.net or a direct subdomain thereof is the condition triggered. In this context, since window.location.href is being checked, it is better to use window.location.host or parse the host from the full URL. Use the browser's built-in URL class to safely extract the hostname, and then perform a direct comparison. This fix only requires changes to the isPlayground function in admin/js/documentate-actions.js, specifically the logic around line 140, and does not require any new dependencies or major refactoring. If subdomains should also match, then use appropriate suffix matching on the host property.


Suggested changeset 1
admin/js/documentate-actions.js

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/admin/js/documentate-actions.js b/admin/js/documentate-actions.js
--- a/admin/js/documentate-actions.js
+++ b/admin/js/documentate-actions.js
@@ -136,8 +136,16 @@
 	 */
 	function isPlayground() {
 		const url = window.location.href;
-		// Check URL patterns
-		if (url.includes('playground.wordpress.net')) return true;
+		// Check if host is exactly 'playground.wordpress.net' or is a subdomain of it
+		try {
+			const host = new URL(url).host;
+			if (
+				host === 'playground.wordpress.net' ||
+				host.endsWith('.playground.wordpress.net')
+			) {
+				return true;
+			}
+		} catch (e) { /* ignore */ }
 		// Check for Playground global
 		if (typeof window.WORDPRESS_PLAYGROUND !== 'undefined') return true;
 		// Check for Playground meta tag
EOF
@@ -136,8 +136,16 @@
*/
function isPlayground() {
const url = window.location.href;
// Check URL patterns
if (url.includes('playground.wordpress.net')) return true;
// Check if host is exactly 'playground.wordpress.net' or is a subdomain of it
try {
const host = new URL(url).host;
if (
host === 'playground.wordpress.net' ||
host.endsWith('.playground.wordpress.net')
) {
return true;
}
} catch (e) { /* ignore */ }
// Check for Playground global
if (typeof window.WORDPRESS_PLAYGROUND !== 'undefined') return true;
// Check for Playground meta tag
Copilot is powered by AI and may make mistakes. Always verify output.
The previous iframe approach failed because iframes inside WordPress
Playground cannot enable Cross-Origin Isolation (COOP/COEP headers).
SharedArrayBuffer is not available without Cross-Origin Isolation.

Changed to open the external converter (erseco.github.io/document-converter)
in a popup window instead. External domain popups may work in Playground
even when internal popups are blocked.

Key changes:
- Replace initExternalConverter() iframe with window.open() popup
- Add polling mechanism to wait for converter ready via postMessage
- Handle conversion result via postMessage from popup
- Close popup automatically after conversion completes

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
Popups don't work in WordPress Playground - they open another Playground
instance instead of the target URL. Changed to use an iframe approach:

- Pre-load the external converter iframe when page loads (Playground mode)
- Use 'credentialless' attribute for potential cross-origin isolation
- Use 'allow="cross-origin-isolated"' feature policy
- Wait for converter to signal ready via postMessage
- Send document for conversion via postMessage to iframe
- Receive result blob via postMessage

Note: SharedArrayBuffer may still not work if the parent document
(Playground) doesn't have Cross-Origin Isolation enabled. The converter
will show an appropriate error in that case.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
Instead of using iframes/popups with postMessage (which fail due to
Cross-Origin Isolation restrictions), now we:

1. Generate the ODT/DOCX document via AJAX
2. Encode the file content as base64
3. Open the external converter in a new tab with the file in URL hash
4. The converter reads the hash, decodes the file, and converts it

Format: https://erseco.github.io/document-converter/#file=BASE64&format=pdf&action=preview

This works because:
- New tabs have their own security context
- The converter's Service Worker can enable Cross-Origin Isolation
- SharedArrayBuffer is available in the converter's context
- No communication needed between tabs (fire and forget)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
The external converter (erseco.github.io/document-converter) now uses
query parameters instead of hash:

API parameters:
- base64: Base64-encoded document content
- name: Filename (required with base64)
- format: Output format (pdf, docx, etc.)
- download: If "true", auto-download; otherwise shows preview

Usage:
- Preview: ?base64=...&name=doc.odt&format=pdf (shows inline)
- Download: ?base64=...&name=doc.odt&format=pdf&download=true

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
Changed from URL parameters to postMessage for sending files to the
external converter. This avoids URL length limits (error 414) when
converting large documents with embedded images.

New flow:
1. Generate ODT/DOCX document via AJAX
2. Open converter with ?receive=opener&format=pdf
3. Wait for converter to signal ready via postMessage
4. Send document buffer via postMessage
5. Converter processes and shows result

This approach works with files of any size since the data is sent
via postMessage instead of being encoded in the URL.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants