-
-
Notifications
You must be signed in to change notification settings - Fork 36.1k
Added new DevTools #30870
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
base: dev
Are you sure you want to change the base?
Added new DevTools #30870
Conversation
|
This is amazing! |
|
Some times is not easy to see where in the page the renderer is... Screen.Recording.2025-07-18.at.12.08.59.PM.mov |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull Request Overview
This PR introduces a new and simpler Three.js DevTools extension for Chrome, providing debugging capabilities for Three.js applications. The extension creates a DevTools panel that allows inspection of scenes, objects, and renderers with real-time updates and interactive features like canvas scrolling and floating object details.
Key changes include:
- Complete Chrome extension architecture with background script, content script, and DevTools panel
- Bridge script for direct Three.js integration and object monitoring
- Interactive UI with collapsible renderer details and floating object inspection panels
Reviewed Changes
Copilot reviewed 10 out of 13 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
| devtools/panel/panel.js | Main panel JavaScript implementing UI rendering, object hierarchy display, and floating details functionality |
| devtools/panel/panel.html | Simple HTML structure for the DevTools panel |
| devtools/panel/panel.css | Comprehensive styling with light/dark theme support and responsive design |
| devtools/manifest.json | Chrome extension manifest defining permissions and entry points |
| devtools/index.html | DevTools page entry point |
| devtools/devtools.js | Script to create the Three.js panel in Chrome DevTools |
| devtools/content-script.js | Content script handling bridge injection and message relay |
| devtools/bridge.js | Bridge script for Three.js integration and object observation |
| devtools/background.js | Background service worker managing extension lifecycle and communications |
| devtools/README.md | Comprehensive documentation of extension architecture and usage |
| let objectCount = - 1; | ||
| function countObjects( uuid ) { | ||
|
|
||
| const object = state.objects.get( uuid ); | ||
| if ( object ) { | ||
|
|
||
| objectCount ++; // Increment count for the object itself |
Copilot
AI
Jul 22, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Starting the object count at -1 and incrementing is confusing. Consider starting at 0 and adjusting the logic or adding a comment explaining why -1 is used as the initial value.
| let objectCount = - 1; | |
| function countObjects( uuid ) { | |
| const object = state.objects.get( uuid ); | |
| if ( object ) { | |
| objectCount ++; // Increment count for the object itself | |
| let objectCount = 0; | |
| function countObjects( uuid ) { | |
| const object = state.objects.get( uuid ); | |
| if ( object ) { | |
| objectCount++; // Increment count for the object itself |
| function generateUUID() { | ||
|
|
||
| const array = new Uint8Array( 16 ); | ||
| crypto.getRandomValues( array ); |
Copilot
AI
Jul 22, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The generateUUID function doesn't handle cases where crypto.getRandomValues might not be available (e.g., in insecure contexts). Consider adding error handling or a fallback method.
| crypto.getRandomValues( array ); | |
| try { | |
| if (crypto && typeof crypto.getRandomValues === 'function') { | |
| crypto.getRandomValues( array ); | |
| } else { | |
| // Fallback to Math.random for insecure contexts | |
| for (let i = 0; i < array.length; i++) { | |
| array[i] = Math.floor(Math.random() * 256); | |
| } | |
| } | |
| } catch (error) { | |
| // Fallback in case of unexpected errors | |
| for (let i = 0; i < array.length; i++) { | |
| array[i] = Math.floor(Math.random() * 256); | |
| } | |
| } |
| const array = new Uint8Array( 16 ); | ||
| crypto.getRandomValues( array ); | ||
| array[ 6 ] = ( array[ 6 ] & 0x0f ) | 0x40; // Set version to 4 | ||
| array[ 8 ] = ( array[ 8 ] & 0x3f ) | 0x80; // Set variant to 10 | ||
| return [ ...array ].map( ( b, i ) => ( i === 4 || i === 6 || i === 8 || i === 10 ? '-' : '' ) + b.toString( 16 ).padStart( 2, '0' ) ).join( '' ); | ||
|
|
Copilot
AI
Jul 22, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The UUID generation logic is complex and hard to read. Consider breaking this into multiple lines or using a more readable approach with clear variable names for better maintainability.
| const array = new Uint8Array( 16 ); | |
| crypto.getRandomValues( array ); | |
| array[ 6 ] = ( array[ 6 ] & 0x0f ) | 0x40; // Set version to 4 | |
| array[ 8 ] = ( array[ 8 ] & 0x3f ) | 0x80; // Set variant to 10 | |
| return [ ...array ].map( ( b, i ) => ( i === 4 || i === 6 || i === 8 || i === 10 ? '-' : '' ) + b.toString( 16 ).padStart( 2, '0' ) ).join( '' ); | |
| // Generate 16 random bytes | |
| const randomBytes = new Uint8Array( 16 ); | |
| crypto.getRandomValues( randomBytes ); | |
| // Set the UUID version to 4 (0100 in binary) | |
| const versionByte = ( randomBytes[ 6 ] & 0x0f ) | 0x40; | |
| randomBytes[ 6 ] = versionByte; | |
| // Set the UUID variant to 10 (binary) | |
| const variantByte = ( randomBytes[ 8 ] & 0x3f ) | 0x80; | |
| randomBytes[ 8 ] = variantByte; | |
| // Convert bytes to hexadecimal and format as UUID | |
| const uuid = randomBytes.map( ( byte, index ) => { | |
| const hex = byte.toString( 16 ).padStart( 2, '0' ); | |
| return ( index === 4 || index === 6 || index === 8 || index === 10 ? '-' : '' ) + hex; | |
| } ).join( '' ); | |
| return uuid; |
| header.style.justifyContent = 'space-between'; // Align items left and right | ||
|
|
||
| const miscSpan = document.createElement( 'span' ); | ||
| miscSpan.innerHTML = '<a href="https://docs.google.com/forms/d/e/1FAIpQLSdw1QcgXNiECYiPx6k0vSQRiRe0FmByrrojV4fgeL5zzXIiCw/viewform?usp=preview" target="_blank">+</a>'; |
Copilot
AI
Jul 22, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Using innerHTML with external URLs could pose security risks. Consider using textContent and addEventListener instead of inline HTML with external links.
| miscSpan.innerHTML = '<a href="https://docs.google.com/forms/d/e/1FAIpQLSdw1QcgXNiECYiPx6k0vSQRiRe0FmByrrojV4fgeL5zzXIiCw/viewform?usp=preview" target="_blank">+</a>'; | |
| const link = document.createElement('a'); | |
| link.href = 'https://docs.google.com/forms/d/e/1FAIpQLSdw1QcgXNiECYiPx6k0vSQRiRe0FmByrrojV4fgeL5zzXIiCw/viewform?usp=preview'; | |
| link.target = '_blank'; | |
| link.textContent = '+'; | |
| miscSpan.appendChild(link); |
devtools/bridge.js
Outdated
| function traverseForBatch( currentObj ) { | ||
|
|
||
| if ( ! currentObj || ! currentObj.uuid || processedUUIDs.has( currentObj.uuid ) ) return; | ||
| processedUUIDs.add( currentObj.uuid ); | ||
|
|
||
| const objectData = getObjectData( currentObj ); | ||
| if ( objectData ) { | ||
|
|
||
| batchObjects.push( objectData ); | ||
| devTools.objects.set( currentObj.uuid, objectData ); // Update local cache during batch creation | ||
|
|
||
| } | ||
|
|
||
| // Process children | ||
| if ( currentObj.children && Array.isArray( currentObj.children ) ) { | ||
|
|
||
| currentObj.children.forEach( child => traverseForBatch( child ) ); | ||
|
|
Copilot
AI
Jul 22, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The recursive traverseForBatch function could cause stack overflow for deeply nested scene graphs. Consider implementing an iterative approach using a stack or queue for better performance and safety.
| function traverseForBatch( currentObj ) { | |
| if ( ! currentObj || ! currentObj.uuid || processedUUIDs.has( currentObj.uuid ) ) return; | |
| processedUUIDs.add( currentObj.uuid ); | |
| const objectData = getObjectData( currentObj ); | |
| if ( objectData ) { | |
| batchObjects.push( objectData ); | |
| devTools.objects.set( currentObj.uuid, objectData ); // Update local cache during batch creation | |
| } | |
| // Process children | |
| if ( currentObj.children && Array.isArray( currentObj.children ) ) { | |
| currentObj.children.forEach( child => traverseForBatch( child ) ); | |
| function traverseForBatch( rootObj ) { | |
| const stack = [ rootObj ]; // Initialize stack with the root object | |
| while ( stack.length > 0 ) { | |
| const currentObj = stack.pop(); // Pop the next object to process | |
| if ( ! currentObj || ! currentObj.uuid || processedUUIDs.has( currentObj.uuid ) ) continue; | |
| processedUUIDs.add( currentObj.uuid ); | |
| const objectData = getObjectData( currentObj ); | |
| if ( objectData ) { | |
| batchObjects.push( objectData ); | |
| devTools.objects.set( currentObj.uuid, objectData ); // Update local cache during batch creation | |
| } | |
| // Add children to the stack for processing | |
| if ( currentObj.children && Array.isArray( currentObj.children ) ) { | |
| stack.push( ...currentObj.children ); | |
| } |
|
Made the extension icon clickable. It scrolls to the found renderer when clicked. Screen.Recording.2025-08-05.at.3.05.22.PM.mov |
|
Is there a roadmap or todo list for the devtools? It's such a promising tool. I'd love to see it show more detailed information about what the renderer is doing when it renders a frame (e.g. draw lists, etc). |
| window.postMessage( { | ||
| id: MESSAGE_ID, | ||
| name: name, | ||
| detail: detail | ||
| }, '*' ); |
Check warning
Code scanning / CodeQL
Cross-window communication with unrestricted target origin Medium
Sensitive data
Sensitive data
Sensitive data
Sensitive data
Sensitive data
Sensitive data
Sensitive data
Sensitive data
Sensitive data
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI 6 days ago
General fix:
Restrict the target origin in calls to window.postMessage, rather than using '*'.
Detailed fix:
We should replace all occurrences of window.postMessage that use a target origin of '*' with a specific origin string, such as 'http://localhost:3000' or another whitelisted value that matches the intended recipient's origin. If the recipient origin is dynamic (e.g., not known ahead of time), establish a handshake protocol (e.g., listen for an initial message that records the caller's origin, validate it, and use that value for subsequent calls).
Implementation for this file:
The responsible code is in the dispatchEvent function.
- Define a constant for the intended target origin that matches the devtools panel or injected script.
- Replace the
'*'argument inwindow.postMessagewith this constant. - If the origin might be dynamic, add logic for capturing the allowed origin during an initial handshake (depending on what you’ve shown of the file), but do not introduce logic that isn’t supported by the code within this snippet.
Required additions:
- Add a constant (e.g.,
TARGET_ORIGIN), set to a value such as'http://localhost:3000'or a comment placeholder (like'SPECIFY_TARGET_ORIGIN_HERE'). - Replace the literal
'*'in the relevant.postMessagecall(s) with that constant.
-
Copy modified lines R10-R11 -
Copy modified line R591
| @@ -7,6 +7,8 @@ | ||
|
|
||
| // Constants | ||
| const MESSAGE_ID = 'three-devtools'; | ||
| // TODO: Set this to the appropriate target origin for postMessage (e.g., 'chrome-extension://<extension-id>' or the devtools panel origin) | ||
| const TARGET_ORIGIN = 'SPECIFY_TARGET_ORIGIN_HERE'; | ||
| const EVENT_REGISTER = 'register'; | ||
| const EVENT_OBSERVE = 'observe'; | ||
| const EVENT_RENDERER = 'renderer'; | ||
| @@ -586,7 +588,7 @@ | ||
| id: MESSAGE_ID, | ||
| name: name, | ||
| detail: detail | ||
| }, '*' ); | ||
| }, TARGET_ORIGIN ); | ||
|
|
||
| } catch ( error ) { | ||
|
|
|
Added yellow wireframe on rollover. Didn't think this was going to be possible. Screen.Recording.2025-11-09.at.12.59.28_.movDoesn't work all the cases but it works on more cases that I thought. |


Description
Started a new and simpler Three.js DevTools.