Skip to content

Conversation

@mho22
Copy link
Collaborator

@mho22 mho22 commented Aug 28, 2025

Motivation for the change, related issues

This pull request aims to apply syntax highlighting to debuggable PHP code using the Xdebug Bridge and Chrome Devtools.

This comes with a downside. To enable syntax highlighting to Debugger's parsed scripts, we also import a copy of that file that will act as the given script. Adding two identical files in the Source panel File tree.

Testing Instructions

First, run the Bridge

nx reset && nx run php-wasm-xdebug-bridge:dev --php-root packages/php-wasm/xdebug-bridge --verbosity debug

Go to Devtools and add a breakpoint.

screenshot-001

Then, run the CLI command

nx reset && nx run php-wasm-cli:dev packages/php-wasm/xdebug-bridge/src/tests/fixtures/test.php --xdebug

It will break in the script, not the resource.

screenshot-002

@mho22 mho22 force-pushed the highlight-php-scripts-from-mime-type-in-devtools branch from b7d38ef to 390ca9f Compare August 28, 2025 10:04
@mho22
Copy link
Collaborator Author

mho22 commented Aug 28, 2025

I could suggest a pull request to the chrome devtools team with these modifications :

diff --git a/front_end/core/sdk/Script.ts b/front_end/core/sdk/Script.ts
index 7bae8f9428..7226993a5d 100644
--- a/front_end/core/sdk/Script.ts
+++ b/front_end/core/sdk/Script.ts
@@ -84,6 +84,7 @@ export class Script implements TextUtils.ContentProvider.ContentProvider, FrameA
   originStackTrace: Protocol.Runtime.StackTrace|null;
   readonly #codeOffsetInternal: number|null;
   readonly #language: string|null;
+  readonly #mime: string|null;
   #contentPromise: Promise<TextUtils.ContentData.ContentDataOrError>|null;
   readonly #embedderNameInternal: Platform.DevToolsPath.UrlString|null;
   readonly isModule: boolean|null;
@@ -93,7 +94,7 @@ export class Script implements TextUtils.ContentProvider.ContentProvider, FrameA
       startLine: number, startColumn: number, endLine: number, endColumn: number, executionContextId: number,
       hash: string, isContentScript: boolean, isLiveEdit: boolean, sourceMapURL: string|undefined,
       hasSourceURL: boolean, length: number, isModule: boolean|null, originStackTrace: Protocol.Runtime.StackTrace|null,
-      codeOffset: number|null, scriptLanguage: string|null, debugSymbols: Protocol.Debugger.DebugSymbols|null,
+      codeOffset: number|null, scriptLanguage: string|null, mimeType: string|null, debugSymbols: Protocol.Debugger.DebugSymbols|null,
       embedderName: Platform.DevToolsPath.UrlString|null, buildId: string|null) {
     this.debuggerModel = debuggerModel;
     this.scriptId = scriptId;
@@ -116,6 +117,7 @@ export class Script implements TextUtils.ContentProvider.ContentProvider, FrameA
     this.originStackTrace = originStackTrace;
     this.#codeOffsetInternal = codeOffset;
     this.#language = scriptLanguage;
+    this.#mime = mimeType;
     this.#contentPromise = null;
     this.#embedderNameInternal = embedderName;
   }
@@ -167,6 +169,10 @@ export class Script implements TextUtils.ContentProvider.ContentProvider, FrameA
     return this.#language;
   }

+  mimeType(): string {
+    return this.#mime || (this.isWasm() ? 'application/wasm' : 'text/javascript');
+  }
+
   executionContext(): ExecutionContext|null {
     return this.debuggerModel.runtimeModel().executionContext(this.executionContextId);
   }
diff --git a/front_end/models/bindings/ResourceScriptMapping.ts b/front_end/models/bindings/ResourceScriptMapping.ts
index b6e539d089..7b2cb82310 100644
--- a/front_end/models/bindings/ResourceScriptMapping.ts
+++ b/front_end/models/bindings/ResourceScriptMapping.ts
@@ -232,7 +232,7 @@ export class ResourceScriptMapping implements DebuggerSourceMapping {
     this.#uiSourceCodeToScriptFile.set(uiSourceCode, scriptFile);
     this.#scriptToUISourceCode.set(script, uiSourceCode);

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

This might be a bit presumptuous of me, since they have no interest in open-sourcing anything other than the Wasm and JS files. Maybe Typescript well?

@adamziel
Copy link
Collaborator

adamziel commented Aug 28, 2025

I could suggest a pull request to the chrome devtools team with these modifications :

Let's try that, who knows! Also CC @ThomasTheDane – we're trying to syntax highlight PHP files in Chrome Devtools. Would you have any interest in a patch such as above to make the source mimeType configurable?

@mho22
Copy link
Collaborator Author

mho22 commented Aug 29, 2025

I tested my suggestion in a local Chrome Devtools and it worked :

screenshot-003

I followed the process to contribute by creating an issue :

https://issues.chromium.org/issues/441711152

@mho22
Copy link
Collaborator Author

mho22 commented Sep 1, 2025

Great! My suggested code change for the DevTools front-end has already been approved by one reviewer! 🎉 Just one more vote to go!

@mho22
Copy link
Collaborator Author

mho22 commented Sep 2, 2025

One reviewer disapproved, asking for modifications, but the error still appear and his suggestions were incorrect. We'll see.

@mho22
Copy link
Collaborator Author

mho22 commented Sep 2, 2025

It doesn't look good. He suggests a way that I can't implement. And it duplicates files again [ like when debugging Typescript files ]. So we have a version with step debugging but code is black and we have syntax highlighting on a file we can't interact with...

@adamziel
Copy link
Collaborator

adamziel commented Sep 2, 2025

The last response in that discussion is:

Forwarding the comment from the CL:

Unfortunately we don't want to get into the habit of supporting third-party debuggers in CDP explicitly. The line gets blurry very quickly and we'll have to support whatever we add indefinitely. This means that on supporting an unknown mimeType field on Debugger.scriptParsed or an unknown scriptLanguage enum variant is out.

Potential workarounds:

Fill in the sourceURL field of the Debugger.scriptParsed. This might not work though, I'm unsure.
Emit an inline source map (as a data URL) for the sourceMappingURL field. The source map would contain a single URL in sources, and mappings would need to be an identity mapping (as both the engine script and the source map source are the same). Note that the upcoming "range mappings proposal" would make such a source map trivial. This is more work on the debuggers' side, but it'll work out of the box without any modification required on the protocol level, or DevTools.

@mho22 so, are you saying that the sourceURL workaround does not work?

I've just tried loading a .js script with a source map referencing a .php file and it triggered the correct syntax highlighting!

CleanShot 2025-09-02 at 15 25 14@2x

Now, that's on a regular index.html page – perhaps would work equally well over CDP? If yes, we'd need to generate 1:1 PHP->PHP source maps on the fly.

Alternatively, we could run a patched version of Chrome devtools-frontend as a standalone app – which would also help us in Playground web. But that's a much larger project.

@mho22
Copy link
Collaborator Author

mho22 commented Sep 2, 2025

@adamziel Yes! That's what I am trying to do, but It doesn't look good. I am trying source mapping but :

  • It duplicates the PHP files
  • I can't set breakpoints on syntax highlighted files.

This is my current script :

for (const [bridgeUri, scriptId] of this.scriptIdByUrl.entries()) {
	const cdpUri = this.uriFromBridgeToCDP(bridgeUri);
	
	const phpContent = await this.readPHPFile(bridgeUri);
	const phpLines = phpContent.split('\n');
	const mappings = phpLines.map(() => 'A').join(';');

	// Minimal PHP source map
	const sourceMap = {
		version: 3,
		sources: [cdpUri],
		sourcesContent: [phpContent],
		mappings
	};

	// Serialize it into a data URI so DevTools can load it
	const sourceMapDataUri =
	'data:application/json;charset=utf-8;base64,' +
	Buffer.from(JSON.stringify(sourceMap)).toString('base64');

	// Load script
	this.cdp.sendMessage({
		method: 'Debugger.scriptParsed',
		params: {
			scriptId,
			url: cdpUri,
			startLine: 0,
			endLine: phpLines.length,
			startColumn: 0,
			endColumn: 0,
			executionContextId: 1,
			isLiveEdit: false,
			sourceMapURL: sourceMapDataUri,
			hasSourceURL: false,
			length: phpContent.length
		},
	});
}

I am missing something... Maybe my mapping?

The suggestion will create duplicates in the Devtools file tree, while my code change in Devtools only enable syntax highlight in files other than wasm and javascript in that same file tree.

Alternatively, we could run a patched version of Chrome devtools-frontend as a standalone app – which would also help us in Playground web. But that's a much larger project.

Yes, that's a big feature 😄

@adamziel
Copy link
Collaborator

adamziel commented Sep 2, 2025

This seems to be the best we can do:

CleanShot 2025-09-02 at 16 24 10@2x

The unhighlighted source files are in the .sources directory. I'm not aware of any way of hiding it. The source maps point to the syntax-highlighted files you can also set breakpoints on in the project/ directory.

Here's how I did it:

# Load PHP source content to embed in sourcesContent
php_script_content = """<?php
function greet() {
echo "Hello from PHP source!\n";
}

greet();
"""

# Prepare inline base64 source map URL for JavaScript, injecting sourcesContent
php_unhighlighted_script_url = "file:///.sources/hello.php"
php_highlighted_script_url = "file:///project/hello.php"
map_obj = {
	"version": 3,
	"file": php_unhighlighted_script_url,
	"sources": [php_highlighted_script_url],
	"sourcesContent": [php_script_content],
	"names": [],
	"mappings": "AAAA;AACA;AACA;AACA;AACA;AACA"
}
encoded_map = base64.b64encode(json.dumps(map_obj).encode("utf-8")).decode("ascii")
js_source_map_url = f"data:application/json;base64,{encoded_map}"

js_script_id = "2"		
js_event = {
	"method": "Debugger.scriptParsed",
	"params": {
		"scriptId": js_script_id,
		"url": php_unhighlighted_script_url,
		"startLine": 0,
		"startColumn": 0,
		"endLine": php_script_content.count('\n'),
		"endColumn": 0,
		"executionContextId": 1,
		"hash": "def456",
		"sourceMapURL": js_source_map_url
	}
}

await websocket.send(json.dumps(js_event))

Full python server source code

@mho22
Copy link
Collaborator Author

mho22 commented Sep 2, 2025

Thank you @adamziel ! I will study your code.

@mho22 mho22 closed this Sep 3, 2025
@mho22 mho22 force-pushed the highlight-php-scripts-from-mime-type-in-devtools branch from 2fb2ee0 to 47d921f Compare September 3, 2025 02:46
@mho22 mho22 reopened this Sep 3, 2025
@mho22
Copy link
Collaborator Author

mho22 commented Sep 3, 2025

In the end, it was easy to implement but some tricks had to be done. Like the identity mapping or the relocation of source and project files. I took the initiative to display things like this :

screenshot-010

@adamziel @brandonpayton What do you think?

@mho22
Copy link
Collaborator Author

mho22 commented Sep 3, 2025

I should add a test.

@mho22
Copy link
Collaborator Author

mho22 commented Sep 3, 2025

I tried to set the whole script code on one line in the source directory to disallow debugging on these files. I find that handy, personally.

screenshot-011

@mho22 mho22 marked this pull request as ready for review September 3, 2025 08:37
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.

LGTM overall, let's just brush up the PR description and some inline notes. Great work @mho22!

@mho22 mho22 merged commit 6cf2966 into trunk Sep 4, 2025
26 checks passed
@mho22 mho22 deleted the highlight-php-scripts-from-mime-type-in-devtools branch September 4, 2025 07:37
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.

3 participants