Skip to content

Debug PHP in browser Devtools (XDebug <-> CDP Bridge) #2288

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 1 commit into
base: trunk
Choose a base branch
from

Conversation

adamziel
Copy link
Collaborator

Motivation for the change, related issues

An XDebug <-> CDP bridge to debug PHP code in the browser:

CleanShot.2025-06-19.at.01.59.06.mp4

Implementation details

I've asked o3-pro to build it, applied a few tweaks, and voila – we have a a demo :-)

Testing Instructions (or ideally a Blueprint)

  • Apply the JSPI PR and this PR.
  • Make sure you're running Node v23+
  • Run:
node --experimental-wasm-stack-switching --experimental-wasm-jspi --loader=./packages/meta/src/node-es-module-loader/loader.mts  packages/xdebug/protocol.ts

The script will guide you from there

Problems

When eval is called, PHP crashes like below. It may be a bridge problem or XDebug problem:

[XDebug][send] eval -- JGNvbnN0cw==
Error: resolved is not a function
    at runExecutionFunction (file:///Users/cloudnik/www/Automattic/core/plugins/playground/packages/php-wasm/universal/src/lib/php.ts:971:22) {
  cause: TypeError: resolved is not a function
      at stubs.<computed> (file:///Users/cloudnik/www/Automattic/core/plugins/playground/packages/php-wasm/node/jspi/php_8_4.js:1296:15)
      at wasm://wasm/000ef1e6:wasm-function[545]:0x203cc
      at wasm://wasm/000ef1e6:wasm-function[604]:0x26320
      at wasm://wasm/000ef1e6:wasm-function[574]:0x228d0
      at wasm://wasm/000ef1e6:wasm-function[578]:0x232ca
      at wasm://wasm/000ef1e6:wasm-function[549]:0x20966
      at wasm://wasm/000ef1e6:wasm-function[219]:0xa209
      at php.wasm.zend_extension_statement_handler (wasm://wasm/php.wasm-08a18d7a:wasm-function[9230]:0x77b902)
      at php.wasm.zend_llist_apply_with_argument (wasm://wasm/php.wasm-08a18d7a:wasm-function[10088]:0x7ee577)
      at php.wasm.ZEND_EXT_STMT_SPEC_HANDLER (wasm://wasm/php.wasm-08a18d7a:wasm-function[9229]:0x77b8df)
}
node:internal/process/promises:394
    triggerUncaughtException(err, true /* fromPromise */);
    ^

Error: resolved is not a function
    at runExecutionFunction (file:///Users/cloudnik/www/Automattic/core/plugins/playground/packages/php-wasm/universal/src/lib/php.ts:971:22) {
  cause: TypeError: resolved is not a function
      at stubs.<computed> (file:///Users/cloudnik/www/Automattic/core/plugins/playground/packages/php-wasm/node/jspi/php_8_4.js:1296:15)
      at wasm://wasm/000ef1e6:wasm-function[545]:0x203cc
      at wasm://wasm/000ef1e6:wasm-function[604]:0x26320
      at wasm://wasm/000ef1e6:wasm-function[574]:0x228d0
      at wasm://wasm/000ef1e6:wasm-function[578]:0x232ca
      at wasm://wasm/000ef1e6:wasm-function[549]:0x20966
      at wasm://wasm/000ef1e6:wasm-function[219]:0xa209
      at php.wasm.zend_extension_statement_handler (wasm://wasm/php.wasm-08a18d7a:wasm-function[9230]:0x77b902)
      at php.wasm.zend_llist_apply_with_argument (wasm://wasm/php.wasm-08a18d7a:wasm-function[10088]:0x7ee577)
      at php.wasm.ZEND_EXT_STMT_SPEC_HANDLER (wasm://wasm/php.wasm-08a18d7a:wasm-function[9229]:0x77b8df)
}

Node.js v23.11.0

cc @mho22 @brandonpayton @akirk @wojtekn @griffbrad @bgrgicak

@adamziel adamziel marked this pull request as draft June 19, 2025 00:00
@adamziel adamziel changed the title XDebug <-> CDP Bridge Debug PHP in Browser's Devtools (XDebug <-> CDP Bridge) Jun 19, 2025
@adamziel adamziel changed the title Debug PHP in Browser's Devtools (XDebug <-> CDP Bridge) Debug PHP in browser Devtools (XDebug <-> CDP Bridge) Jun 19, 2025
@mho22
Copy link
Collaborator

mho22 commented Jun 19, 2025

@adamziel I'm impressed. A great way to begin my day. I need to dive deeper into that protocol.

@brandonpayton
Copy link
Member

@adamziel, this looks awesome!

@adamziel
Copy link
Collaborator Author

adamziel commented Jun 24, 2025

Or should we do WebDriver BiDi instead? Firefox is retiring its CDP support:

https://fxdx.dev/cdp-retirement-in-firefox/

And if you are migrating a tool from CDP to WebDriver BiDi for Firefox, you should also consider using WebDriver BiDi for Chromium based browsers. Chromium has a great support for WebDriver BiDi, and WebKit is starting to implement BiDi as well. So switching to BiDi now is not only a way to keep Firefox support, but it can also pave the way to get more features for all browsers as we keep expanding the standard protocol.

@mho22
Copy link
Collaborator

mho22 commented Jun 26, 2025

Probably? BiDi seems to be a better long-term solution but is not yet ready for WebKit. On the other hand, we would definitely lose Firefox support with CDP.

mho22 added a commit that referenced this pull request Jul 9, 2025
…#2326)

## Motivation for the change

This is a pull request to dynamically load Xdebug in `@php-wasm` Node
ASYNCIFY.

[Roadmap](#2315)

## Related issues and pull requests

Relative to XDebug

- #2248
- #314
- #2288
- #831

Relative to dynamic library

- #89
- #2290
- #673

## Implementation details

Additional steps for asyncify are implemented : 

- new nx commands
- new `WITH_JSPI` and `WITH_DEBUG` arguments in Xdebug Dockerfile
- new `xdebug.so` files are compiled and dynamically loaded

## Zend extensions and PHP versions
> [!IMPORTANT]  
> Each version of PHP requires its own version of Xdebug because each
version of PHP has a specific Zend Engine API version. Consequently,
each version of Xdebug is designed to be compatible with a particular
Zend Engine API version.

Example using PHP `8.2` with Xdebug `8.3` :

```
Xdebug requires Zend Engine API version 420230831.
The Zend Engine API version 420220829 which is installed, is outdated.
 ```


## Testing Instructions

Two files are needed. A PHP file to test the step debugger. A JS file to run PHP 8.4 node ASYNCIFY.

`php/xdebug.php`

```php
<?php

$test = 42; // Set a breakpoint on this line

echo "Hello Xdebug World\n";
```


`scripts/node.js`

```javascript
import { PHP } from '@php-wasm/universal';
import { loadNodeRuntime } from '@php-wasm/node';


const php = new PHP( await loadNodeRuntime( '8.4', { withXdebug : true }
) );

await php.runStream( { scriptPath : `php/xdebug.php` } );
```


To achieve the test, you first need to start debugging [ with F5 or Run > Start debugging in VSCode ], then run :

```bash
node scripts/node.js
```
adamziel added a commit that referenced this pull request Jul 22, 2025
## Motivation for the change, related issues

Adds a mock, AI-generated `@php-wasm/xdebug-bridge` package to jumpstart
the work on [XDebug in browser
devtools](#2288).

Cursor prompt:

> Create a new package `@php-wasm/xdebug-bridge` with a
startXDebugBridge() function that starts a plain network server, It
should accept a few config params via an object, e.g. protocol ("cdp"),
xdebugServerPort (number, default to xdebug default port).
> 
> It should also ship a CLI script that can be used to run that function
from CLI. Accept CLI args, print useful information, but make sure
startXDebugBridge() can still be executed without printing anything to
console.

## Testing Instructions (or ideally a Blueprint)

Confirm it doesn't break the CI.

cc @mho22
adamziel added a commit that referenced this pull request Jul 23, 2025
## Motivation for the change, related issues

Populates the new `php-wasm-xdebug-bridge` package which will connect
Xdebug with Browser Devtools.

[Roadmap](#2315)

## Related issues and pull requests

- #2346
- #2398
- #2288

## Implementation details

- DBGP communication TCP server, which the bridge uses to communication
between PHP.wasm XDebug and the CDP server. XDebug defaultly discusses
on port 9003. While the DBGP server is set on port 9003 by default.

- CDP Websocket Server, which communicates between the Browser Devtools
and the DBGP server, with the help of a bridge. The default port is
9229.

- XDebug <-> CDP Bridge, which ties the above implementations together.
It plays with all the specificities of the DBGP protocol and the CDP
protocol.

- A `startBridge` function that will initialize the servers, get the
soon-to-be-debugged files and return the built bridge.

- A CLI tool that will start the bridge between the browser and a
XDebug.

## Testing Instructions

Create a `xdebug.php` PHP file at the root of the repository :

```php
<?php

$test = 42;  // Set a breakpoint on this line

echo "Output!\n";

function test() {
	echo "Hello Xdebug World!\n";
}

test();
```

### From Wordpress-Playground repository

1. Run the `xdebug-bridge` command in a first terminal : 

```
> npx nx run php-wasm-xdebug-bridge:dev

...

Chrome connected! Initializing Xdebug receiver...
XDebug receiver running on port 9003
Running a PHP script with Xdebug enabled...
```

2. Run the `php-wasm-cli` command in a second terminal : 

```
> nx run php-wasm-cli:dev --xdebug xdebug.php

Output!
Hello Xdebug World!
```

### From non-related Playground project

1. Install dependencies

```
npm install @php-wasm/cli @php-wasm/xdebug-bridge
```

2. Run the `xdebug-bridge` command in a first terminal : 

```
> npx xdebug-bridge

...

Chrome connected! Initializing Xdebug receiver...
XDebug receiver running on port 9003
Running a PHP script with Xdebug enabled...
```

3. Run the `@php-wasm/cli` command in a second terminal : 

```
> npx cli --xdebug xdebug.php

Output!
Hello Xdebug World!
```

<img width="748" height="409" alt="screencapture"
src="https://github.com/user-attachments/assets/b0232831-c9b3-4bb2-95aa-b1d51b72766e"
/>

---------

Co-authored-by: mho22 <[email protected]>
adamziel pushed a commit that referenced this pull request Jul 24, 2025
)

## Motivation for the change, related issues

This pull request adds a `--experimental-devtools` option in
`wp-playground/cli`.

[Roadmap](#2315)

## Related issues and pull requests

- #2408
- #2402
- #2398
- #2346
- #2288

## Implementation details

- Simple implementation of a Devtools option. It checks for a
`experimentalDevtools` argument that starts the `xdebug-to-cdp-bridge`
process.
- Changes the `StartBridgeConfig` `getPHPFile` property type from
`(path: string) => string` to `(path: string) => Promise<string>` and
adapt the related code.

## Testing Instructions

### In WordPress-Playground repository

1. Write a script like the following `cli.ts` : 

```typescript
import { runCLI } from "./packages/playground/cli/src/run-cli";

const script = `
<?php

$test = 42;

echo "Output!\n";

function test() {
	echo "Hello Xdebug World!\n";
}

test();
`;

const cliServer = await runCLI({command: 'server', xdebug: true, experimentalDevtools: true});

cliServer.playground.writeFile('xdebug.php', script);

const result = await cliServer.playground.run({scriptPath: `xdebug.php`});

console.log(result.text);
```

2. Run the following command : 

```
> node --no-warnings --experimental-wasm-stack-switching --experimental-wasm-jspi --loader=./packages/meta/src/node-es-module-loader/loader.mts cli.ts

Starting a PHP server...
Setting up WordPress undefined
Resolved WordPress release URL: https://downloads.w.org/release/wordpress-6.8.2.zip
Fetching SQLite integration plugin...
Booting WordPress...
Booted!
Running the Blueprint...
Running the Blueprint – 100%
Finished running the blueprint
WordPress is running on http://127.0.0.1:61290
Connect Chrome DevTools to CDP at:
devtools://devtools/bundled/inspector.html?ws=localhost:9229

...

Chrome connected! Initializing Xdebug receiver...
XDebug receiver running on port 9003
Running a PHP script with Xdebug enabled...
```

### In a non-related Playground project

1. Install dependencies

```
npm install @wp-playground/cli
```

2. Write a script like the following `cli.ts` :

```typescript
import { runCLI } from "@wp-playground/cli";

const script = `
<?php

$test = 42;

echo "Output!\n";

function test() {
	echo "Hello Xdebug World!\n";
}

test();
`;

const cliServer = await runCLI({command: 'server',  xdebug: true, experimentalDevtools: true});

await cliServer.playground.writeFile(`xdebug.php`, script);

const result = await cliServer.playground.run({scriptPath: `xdebug.php`});

console.log(result.text);
```

3. Run command : 

```
> node cli.js

Starting a PHP server...
Setting up WordPress undefined
Resolved WordPress release URL: https://downloads.w.org/release/wordpress-6.8.2.zip
Fetching SQLite integration plugin...
Booting WordPress...
Booted!
Running the Blueprint...
Running the Blueprint – 100%
Finished running the blueprint
WordPress is running on http://127.0.0.1:61859
Connect Chrome DevTools to CDP at:
devtools://devtools/bundled/inspector.html?ws=localhost:9229

...

Chrome connected! Initializing Xdebug receiver...
XDebug receiver running on port 9003
Running a PHP script with Xdebug enabled...
```

<img width="920" height="471" alt="screencapture"
src="https://github.com/user-attachments/assets/b3548d0e-f824-41c4-9148-3e4f106b4116"
/>

Note: It will need 23 step overs before leaving the
`auto_prepend_file.php` and entering the `xdebug.php` script.
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.

3 participants