Skip to content

Conversation

@mho22
Copy link
Collaborator

@mho22 mho22 commented Aug 18, 2025

Motivation for the change, related issues

Based on the following pull request :

The first approach was to load Devtools when running a file with Xdebug enabled PHP.wasm. Unfortunately, the user experience was not great. e.g. files were loaded only when executed.

The new approach opens de Devtools Source panel with the relevant loaded files ready to be manipulated.
Once the breakpoints are set, the PHP file can be executed and paused with Xdebug enabled PHP.wasm.

Implementation details

  • DevTools Sources Opening : Devtools Source panel is automatically opened when bridge starts.
  • Console Instructions : A startup message guides users on how to debug with the bridge.
  • Source File Loading : Relevant PHP files are preloaded in DevTools for inspection and breakpoints.
  • Pending Breakpoints : Breakpoints set before Xdebug init are applied once the session starts.
  • URI Normalization : Paths are consistently mapped between Bridge, CDP, and DBGP.
  • CDP Command Buffer : CDP requests are buffered until the bridge is fully initialized.
  • breakOnFirstLine Option : Allows breaking on the first executed line when no breakpoints exist.
  • Test Coverage

Testing Instructions with Xdebug Bridge

  1. Run the devtools
npx xdebug-bridge
  1. Connect to the devtools
Starting XDebug Bridge...
Connect Chrome DevTools to CDP at:
devtools://devtools/bundled/inspector.html?ws=localhost:9229
  1. Set a breakpoint in a file
screenshot-001
  1. Run the PHP script with PHP.wasm CLI and Xdebug option
npx php-wasm-cli test.php --xdebug
screenshot-002
  1. Resume script execution and repeat step 4 indefinitely
screenshot-003

Testing Instructions with PHP.wasm CLI

  1. Run the script with Xdebug and Devtools options
npx php-wasm-cli test.php --experimental-devtools --xdebug
  1. Connect to the devtools
Starting XDebug Bridge...
Connect Chrome DevTools to CDP at:
devtools://devtools/bundled/inspector.html?ws=localhost:9229
  1. It will pause on the first breakable PHP code
screenshot-002

Testing Instructions with PHP.wasm Node

  1. Write the following script
import { PHP } from '@php-wasm/universal';
import { loadNodeRuntime } from '@php-wasm/node';
import { startBridge } from '@php-wasm/xdebug-bridge';


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

const bridge = await startBridge({phpInstance: php, breakOnFirstLine: true});

bridge.start();

await php.runStream({scriptPath: `test.php`});
  1. Run the script with Node
node script.js
  1. Connect to the devtools
Starting XDebug Bridge...
Connect Chrome DevTools to CDP at:
devtools://devtools/bundled/inspector.html?ws=localhost:9229
  1. It will pause on the first breakable PHP code
screenshot-002

Testing Instructions in wordpress-playground

  1. Run the devtools
nx reset && nx run php-wasm-xdebug-bridge:dev --php-root /absolute/path/to/the/debuggable/directory
  1. Connect to the devtools
Starting XDebug Bridge...
Connect Chrome DevTools to CDP at:
devtools://devtools/bundled/inspector.html?ws=localhost:9229
  1. Set a breakpoint in a file
screenshot-001
  1. Run the PHP script
nx reset && nx run php-wasm-cli:dev /absolute/path/to/the/debuggable/directory/file.php --xdebug
screenshot-002
  1. Resume script execution and repeat step 4 indefinitely
screenshot-003

…reakpoints when PHP script is run with Xdebug enabled
@brandonpayton brandonpayton requested a review from a team August 20, 2025 17:49
@mho22
Copy link
Collaborator Author

mho22 commented Aug 20, 2025

I think the user experience is cool. You load your files, you set breakpoints, you run your code with PHP runtime and Xdebug enabled and everytime you run your code it breaks on the lines you chose. Really handy.

However, I noticed two visual caveats :

  • No CSS to stylize logs since we are not using a JS runtime.
  • No PHP syntax highlights inside Sources Devtools Codemirror panel. [ HTML,CSS and JS only ].

I was thinking of something :

In PHP.wasm CLI, when we add the --experimental-devtools option, with the current behavior, it should open the Devtools and directly run the code you wanted to run with the CLI without pausing on breakpoints since there is none. This is not really what we are looking for. I think the best answer would be to break on the first line in this situation. So a new option has to be added in the bridge for that use case. something like : autoBreak?

I also need to implement tests before setting it ready for review.

@brandonpayton
Copy link
Member

In PHP.wasm CLI, when we add the --experimental-devtools option, with the current behavior, it should open the Devtools and directly run the code you wanted to run with the CLI without pausing on breakpoints since there is none. This is not really what we are looking for. I think the best answer would be to break on the first line in this situation. So a new option has to be added in the bridge for that use case. something like : autoBreak?

This sounds like a good feature. I wonder an option like autoBreak could take different values that mean things like "always break" or "break if no breakpoints set".

I think it may be clearer to use a name for the option that indicates "when". Some possibilities might be "breakOnLoad" or "breakOnStartup".

Copy link
Member

@brandonpayton brandonpayton left a comment

Choose a reason for hiding this comment

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

Thanks, @mho22! This tests well for me and is very cool.

const { url, lineNumber } = params;
const fileUri = url;
const file = this.uriFromCDPToBridge(url);
const uri = this.uriFromBridgeToDBGP(file);
Copy link
Member

Choose a reason for hiding this comment

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

When reading this method and its references to url, file, and uri, my working memory has to remember the intention behind each of the generic names in order to best understand the code. Could we try to reduce that cognitive load by using more specific variable names that mention their purpose?

Update:
After reading more code within this module, it is possible that the generic variable names are used throughout. If that is the case, maybe now is not the time to change them.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

You're right. I am juggling with url, uri, file, fileUri. I unfortunately need to use three types of "uris" :

  1. The filePath in the Node Filesystem to find the source code.
  2. The absolute path of the file because Xdebug works only with absolute paths
  3. The relative path indicated when starting the Bridge.

I will try to clean up this.

@brandonpayton
Copy link
Member

brandonpayton commented Aug 22, 2025

  • No PHP syntax highlights inside Sources Devtools Codemirror panel. [ HTML,CSS and JS only ].

Highlighting also works for C/C++ when using the C/C++ DevTools Support (DWARF) Chrome extension:
image

I've poked around the local extension code for Google's Wasm debugging extension and so far haven't seen anything special in the extension for adding syntax highlighting. Maybe Google just added support for syntax highlighting those file types as part of Chrome. :-/

I also experimented with providing a different URL with the Debugger.getScriptSource response. I wondered if CodeMirror was just missing a recognizable MIME type when opening the PHP file, so I started a static file server and started sending "http://localhost:3000"-based URLs instead of "file://" URLs. The static file server sent the PHP file content with a Content-Type of application/x-httpd-php which is a type CodeMirror respects for PHP highlighting. But it didn't seem to help. The devtools showed files loaded from http://localhost:3000, but the contents were still not highlighted.

@brandonpayton
Copy link
Member

brandonpayton commented Aug 22, 2025

I also experimented with providing a different URL with the Debugger.getScriptSource response. I wondered if CodeMirror was just missing a recognizable MIME type when opening the PHP file, so I started a static file server and started sending "http://localhost:3000"-based URLs instead of "file://" URLs. The static file server sent the PHP file content with a Content-Type of application/x-httpd-php which is a type CodeMirror respects for PHP highlighting. But it didn't seem to help. The devtools showed files loaded from http://localhost:3000, but the contents were still not highlighted.

@mho22 It seems like syntax-highlighting based on MIME type ought to work if my scanning of the Chromium devtools source is correct.

There is a case to select the PHP language in the CodeHighlighter.ts based on the application/x-httpd-php MIME type:
https://chromium.googlesource.com/devtools/devtools-frontend/+/5b24e680e08f6637f022b7895da4e50ddd26b13e/front_end/ui/components/code_highlighter/CodeHighlighter.ts#92

They even have a test for it:
https://chromium.googlesource.com/devtools/devtools-frontend/+/5b1eaf864f5487dee52d5800cc672e43d747c118/test/unittests/front_end/ui/components/CodeHighlighter_test.ts#109

Maybe there is still hope that we can load and syntax-highlight PHP files if we can effectively relay the MIME type to devtools.

@mho22
Copy link
Collaborator Author

mho22 commented Aug 25, 2025

@brandonpayton Thank you for your investigation! I actually also found these informations and I was looking for a languageFromMIME method but none were called in the Source tab. I finally found something in this "legacy" file SourceFrame.ts.

  protected async getLanguageSupport(content: string|CodeMirror.Text): Promise<CodeMirror.Extension> {
    // This is a pretty horrible work-around for webpack-based Vue2 setups. See
    // https://crbug.com/1416562 for the full story behind this.
    let {contentType} = this;
    if (contentType === 'text/x.vue') {
      content = typeof content === 'string' ? content : content.sliceString(0);
      if (!content.trimStart().startsWith('<')) {
        contentType = 'text/javascript';
      }
    }
    const languageDesc = await CodeHighlighter.CodeHighlighter.languageFromMIME(contentType);
    if (!languageDesc) {
      return [];
    }
    return [
      languageDesc,
      CodeMirror.javascript.javascriptLanguage.data.of({autocomplete: CodeMirror.completeAnyWord}),
    ];
  }

I first thought, as a legacy file, that it won't be related to the Source panel. I was wrong. But the current way to send our script source to the Devtools tool was by using the Debugger.scriptParsed event. Which will always call the addScript private method from the ResourceScriptMapping.ts:

  // Bind UISourceCode to scripts.
  const scriptFile = new ResourceScriptFile(this, uiSourceCode, script);
  this.#uiSourceCodeToScriptFile.set(uiSourceCode, scriptFile);
  this.#scriptToUISourceCode.set(script, uiSourceCode);

  const mimeType = script.isWasm() ? 'application/wasm' : 'text/javascript';
  project.addUISourceCodeWithProvider(uiSourceCode, originalContentProvider, metadata, mimeType);
  void this.debuggerWorkspaceBinding.updateLocations(script);

And set the mimeType as text/javascript.

I tried one last thing by using something different than Debugger.scriptParsed : communicating with the Network and... 🎉

screenshot-001

My current code implementation is a complete mess. I need to clean it up before pushing. I am still not sure if everything will work like with Debugger.scriptParsed but I’ll make sure to test everything tomorrow.

@brandonpayton
Copy link
Member

I tried one last thing by using something different than Debugger.scriptParsed : communicating with the Network and... 🎉

This is great, @mho22! What did you end up using instead? I'm very interested. 🍿

@mho22
Copy link
Collaborator Author

mho22 commented Aug 26, 2025

I used a serie of events to replace Debugger.scriptParsed :

- Page.frameNavigated
- Runtime.executionContextCreated
- Network.requestWillBeSent
- Network.responseReceived
- Network.loadingFinished

And in order to replace Debugger.getScriptSource :

- Network.getResponseBody

I have a lot of work to do to find out if all these steps are necessary, if the step debugging still works and how I can simplify it the most but seeing the colored code was delightful. However, this will be a separate pull request.

@mho22
Copy link
Collaborator Author

mho22 commented Aug 26, 2025

I still need to do the following:

  • Resolve conflicts.
  • Add a new bridge option breakOnFirstLine in order to make the PHP-WASM CLI work.
  • Clear the uri confusion.
  • Add a test that loads a file, adds a breakpoint, runs a php runtime with Xdebug enabled and should stop at the breakpoint.
  • Add a test that breaks automatically on the first line when the bridge option breakOnFirstLine is set.

@mho22 mho22 marked this pull request as ready for review August 27, 2025 15:46
@adamziel
Copy link
Collaborator

Hey, look at that – I'm debugging WordPress!

CleanShot 2025-08-28 at 12 52 25@2x

entry: {
source: 'other',
level: 'info',
text: '🎉 Welcome to WordPress Playground DevTools! 🎉\n ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾\n\n1. Add breakpoints in your files to start step debugging.\n\n2. Run your php file, project, plugin or theme using PHP.wasm or the Playground.\n\n3. Witness the magic break.',
Copy link
Collaborator

Choose a reason for hiding this comment

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

Let's explain how to run them. Inline instructions would be convenient if we can commit to the command and avoid any BC-breakes. A link might also be useful. CC @fellyph to coordinate a doc page with examples.

Also – "the Playground" makes it seem like playground.wordpress.net, if that's about Playground CLI, let's say it explicitly.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

More I am thinking about this more I find ways to run a php code with Xdebug and the wordpress-playground materials. So, I have two suggestions :

  1. We could either add a link but we could implement it in another pull request in coordination with @fellyph. Something like this :
screenshot-009
  1. I could add the smallest possible way to run Xdebug to not fill the console. Something like this :
screenshot-010

But as we cannot pretty print the logs in the console.log. This makes it not easy to read. I would personally prefer the first suggestion.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Combining both would be nice, as in "Here's how to run it: (command). For more details go here:". That way you can still do something when you're offline.

Here's a weird, alternative idea – we could ship the documentation offline with the CLI package. It could either be the entire docsite, which would be large, or just the markdown files – potentially with a man-like CLI reader. Then we could reference the local resource. But that's way beyond the scope of this PR. cc @fellyph and @dmsnell for thoughts.

Copy link
Collaborator

Choose a reason for hiding this comment

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

It's not a blocker here by the way, we can follow up with an improvement

Copy link
Collaborator

@adamziel adamziel left a comment

Choose a reason for hiding this comment

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

This is seriously cool and super useful! I left a few nitpicks and I think this is good to go.

Let's just update the PR description and document the commands needed to use it outside of the Playground repo (with npx). They will come handy once it's time to write a doc page and a blog post about this feature.

@mho22
Copy link
Collaborator Author

mho22 commented Aug 29, 2025

This is seriously cool and super useful! I left a few nitpicks and I think this is good to go.

🎉

Let's just update the PR description and document the commands needed to use it outside of the Playground repo (with npx). They will come handy once it's time to write a doc page and a blog post about this feature.

On it.

@mho22
Copy link
Collaborator Author

mho22 commented Sep 1, 2025

@adamziel I think we are good here if you agree with my suggestion to create a proper documentation in another pull request.

@adamziel
Copy link
Collaborator

adamziel commented Sep 2, 2025

Looks good overall and solves an important problem that gets us closer to dependency-less usage in Playground CLI – let's get it in! Thank you @mho22!

@adamziel adamziel merged commit ec1ed3a into trunk Sep 2, 2025
26 checks passed
@adamziel adamziel deleted the display-working-directory-files-in-devtools branch September 2, 2025 13:11
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