Mr.doob 3 meses atrás
pai
commit
7b5ab33fc7
5 arquivos alterados com 324 adições e 244 exclusões
  1. 46 20
      devtools/background.js
  2. 111 82
      devtools/bridge.js
  3. 20 14
      devtools/content-script.js
  4. 1 1
      devtools/manifest.json
  5. 146 127
      devtools/panel/panel.js

+ 46 - 20
devtools/background.js

@@ -1,5 +1,14 @@
 /* global chrome */
 /* global chrome */
 
 
+// Constants
+const MESSAGE_ID = 'three-devtools';
+const MESSAGE_INIT = 'init';
+const MESSAGE_REQUEST_STATE = 'request-state';
+const MESSAGE_REQUEST_OBJECT_DETAILS = 'request-object-details';
+const MESSAGE_SCROLL_TO_CANVAS = 'scroll-to-canvas';
+const MESSAGE_REGISTER = 'register';
+const MESSAGE_COMMITTED = 'committed';
+
 // Map tab IDs to connections
 // Map tab IDs to connections
 const connections = new Map();
 const connections = new Map();
 
 
@@ -8,11 +17,13 @@ chrome.action.onClicked.addListener( ( tab ) => {
 
 
 	// Send scroll-to-canvas message to the content script (no UUID = scroll to first canvas)
 	// Send scroll-to-canvas message to the content script (no UUID = scroll to first canvas)
 	chrome.tabs.sendMessage( tab.id, {
 	chrome.tabs.sendMessage( tab.id, {
-		name: 'scroll-to-canvas',
+		name: MESSAGE_SCROLL_TO_CANVAS,
 		tabId: tab.id
 		tabId: tab.id
 	} ).catch( () => {
 	} ).catch( () => {
-		// Tab might not have the content script injected
+
+		// Ignore error - tab might not have the content script injected
 		console.log( 'Could not send scroll-to-canvas message to tab', tab.id );
 		console.log( 'Could not send scroll-to-canvas message to tab', tab.id );
+
 	} );
 	} );
 
 
 } );
 } );
@@ -22,23 +33,18 @@ chrome.runtime.onConnect.addListener( port => {
 
 
 	let tabId;
 	let tabId;
 
 
+	// Messages that should be forwarded to content script
+	const forwardableMessages = new Set( [ MESSAGE_REQUEST_STATE, MESSAGE_REQUEST_OBJECT_DETAILS, MESSAGE_SCROLL_TO_CANVAS ] );
+
 	// Listen for messages from the devtools panel
 	// Listen for messages from the devtools panel
 	port.onMessage.addListener( message => {
 	port.onMessage.addListener( message => {
 
 
-		if ( message.name === 'init' ) {
+		if ( message.name === MESSAGE_INIT ) {
 
 
 			tabId = message.tabId;
 			tabId = message.tabId;
 			connections.set( tabId, port );
 			connections.set( tabId, port );
 
 
-		} else if ( message.name === 'request-state' && tabId ) {
-
-			chrome.tabs.sendMessage( tabId, message );
-
-		} else if ( message.name === 'request-object-details' && tabId ) {
-
-			chrome.tabs.sendMessage( tabId, message );
-
-		} else if ( message.name === 'scroll-to-canvas' && tabId ) {
+		} else if ( forwardableMessages.has( message.name ) && tabId ) {
 
 
 			chrome.tabs.sendMessage( tabId, message );
 			chrome.tabs.sendMessage( tabId, message );
 
 
@@ -81,15 +87,27 @@ chrome.runtime.onMessage.addListener( ( message, sender, sendResponse ) => {
 		const tabId = sender.tab.id;
 		const tabId = sender.tab.id;
 
 
 		// If three.js is detected, show a badge
 		// If three.js is detected, show a badge
-		if ( message.name === 'register' && message.detail && message.detail.revision ) {
+		if ( message.name === MESSAGE_REGISTER && message.detail && message.detail.revision ) {
 
 
 			const revision = String( message.detail.revision );
 			const revision = String( message.detail.revision );
 			const number = revision.replace( /\D+$/, '' );
 			const number = revision.replace( /\D+$/, '' );
 			const isDev = revision.includes( 'dev' );
 			const isDev = revision.includes( 'dev' );
 
 
-			chrome.action.setBadgeText( { tabId: tabId, text: number } ).catch( () => { /* Tab might be gone */ } );
-			chrome.action.setBadgeTextColor( { tabId: tabId, color: '#ffffff' } ).catch( () => { /* Tab might be gone */ } );
-			chrome.action.setBadgeBackgroundColor( { tabId: tabId, color: isDev ? '#ff0098' : '#049ef4' } ).catch( () => { /* Tab might be gone */ } );
+			chrome.action.setBadgeText( { tabId: tabId, text: number } ).catch( () => {
+
+				// Ignore error - tab might have been closed
+
+			} );
+			chrome.action.setBadgeTextColor( { tabId: tabId, color: '#ffffff' } ).catch( () => {
+
+				// Ignore error - tab might have been closed
+
+			} );
+			chrome.action.setBadgeBackgroundColor( { tabId: tabId, color: isDev ? '#ff0098' : '#049ef4' } ).catch( () => {
+
+				// Ignore error - tab might have been closed
+
+			} );
 
 
 		}
 		}
 
 
@@ -127,7 +145,11 @@ chrome.webNavigation.onCommitted.addListener( details => {
 	// Clear badge on navigation, only for top-level navigation
 	// Clear badge on navigation, only for top-level navigation
 	if ( frameId === 0 ) {
 	if ( frameId === 0 ) {
 
 
-		chrome.action.setBadgeText( { tabId: tabId, text: '' } ).catch( () => { /* Tab might be gone */ } );
+		chrome.action.setBadgeText( { tabId: tabId, text: '' } ).catch( () => {
+
+			// Ignore error - tab might have been closed
+
+		} );
 
 
 	}
 	}
 
 
@@ -136,8 +158,8 @@ chrome.webNavigation.onCommitted.addListener( details => {
 	if ( port ) {
 	if ( port ) {
 
 
 		port.postMessage( {
 		port.postMessage( {
-			id: 'three-devtools',
-			name: 'committed',
+			id: MESSAGE_ID,
+			name: MESSAGE_COMMITTED,
 			frameId: frameId
 			frameId: frameId
 		} );
 		} );
 
 
@@ -148,7 +170,11 @@ chrome.webNavigation.onCommitted.addListener( details => {
 // Clear badge when a tab is closed
 // Clear badge when a tab is closed
 chrome.tabs.onRemoved.addListener( ( tabId ) => {
 chrome.tabs.onRemoved.addListener( ( tabId ) => {
 
 
-	chrome.action.setBadgeText( { tabId: tabId, text: '' } ).catch( () => { /* Tab might be gone */ } );
+	chrome.action.setBadgeText( { tabId: tabId, text: '' } ).catch( () => {
+
+		// Ignore error - tab is already gone
+
+	} );
 
 
 	// Clean up connection if it exists for the closed tab
 	// Clean up connection if it exists for the closed tab
 	if ( connections.has( tabId ) ) {
 	if ( connections.has( tabId ) ) {

+ 111 - 82
devtools/bridge.js

@@ -5,6 +5,18 @@
 
 
 ( function () {
 ( function () {
 
 
+	// Constants
+	const MESSAGE_ID = 'three-devtools';
+	const EVENT_REGISTER = 'register';
+	const EVENT_OBSERVE = 'observe';
+	const EVENT_RENDERER = 'renderer';
+	const EVENT_SCENE = 'scene';
+	const EVENT_OBJECT_DETAILS = 'object-details';
+	const EVENT_DEVTOOLS_READY = 'devtools-ready';
+	const MESSAGE_REQUEST_STATE = 'request-state';
+	const MESSAGE_REQUEST_OBJECT_DETAILS = 'request-object-details';
+	const MESSAGE_SCROLL_TO_CANVAS = 'scroll-to-canvas';
+
 	// Only initialize if not already initialized
 	// Only initialize if not already initialized
 	if ( ! window.__THREE_DEVTOOLS__ ) {
 	if ( ! window.__THREE_DEVTOOLS__ ) {
 
 
@@ -26,9 +38,9 @@
 
 
 				// If this is the first listener for a type, and we have backlogged events,
 				// If this is the first listener for a type, and we have backlogged events,
 				// check if we should process them
 				// check if we should process them
-				if ( type !== 'devtools-ready' && this._backlog.length > 0 ) {
+				if ( type !== EVENT_DEVTOOLS_READY && this._backlog.length > 0 ) {
 
 
-					this.dispatchEvent( new CustomEvent( 'devtools-ready' ) );
+					this.dispatchEvent( new CustomEvent( EVENT_DEVTOOLS_READY ) );
 
 
 				}
 				}
 
 
@@ -36,9 +48,9 @@
 
 
 			dispatchEvent( event ) {
 			dispatchEvent( event ) {
 
 
-				if ( this._ready || event.type === 'devtools-ready' ) {
+				if ( this._ready || event.type === EVENT_DEVTOOLS_READY ) {
 
 
-					if ( event.type === 'devtools-ready' ) {
+					if ( event.type === EVENT_DEVTOOLS_READY ) {
 
 
 						this._ready = true;
 						this._ready = true;
 						const backlog = this._backlog;
 						const backlog = this._backlog;
@@ -92,6 +104,35 @@
 		const observedRenderers = [];
 		const observedRenderers = [];
 		const sceneObjectCountCache = new Map(); // Cache for object counts per scene
 		const sceneObjectCountCache = new Map(); // Cache for object counts per scene
 
 
+		// Shared tree traversal function
+		function traverseObjectTree( rootObject, callback, skipDuplicates = false ) {
+
+			const processedUUIDs = skipDuplicates ? new Set() : null;
+
+			function traverse( object ) {
+
+				if ( ! object || ! object.uuid ) return;
+
+				// Skip if already processed (when duplicate prevention is enabled)
+				if ( processedUUIDs && processedUUIDs.has( object.uuid ) ) return;
+				if ( processedUUIDs ) processedUUIDs.add( object.uuid );
+
+				// Execute callback for this object
+				callback( object );
+
+				// Process children recursively
+				if ( object.children && Array.isArray( object.children ) ) {
+
+					object.children.forEach( child => traverse( child ) );
+
+				}
+
+			}
+
+			traverse( rootObject );
+
+		}
+
 		// Function to get renderer data
 		// Function to get renderer data
 		function getRendererData( renderer ) {
 		function getRendererData( renderer ) {
 
 
@@ -188,14 +229,14 @@
 		}
 		}
 
 
 		// Listen for Three.js registration
 		// Listen for Three.js registration
-		devTools.addEventListener( 'register', ( event ) => {
+		devTools.addEventListener( EVENT_REGISTER, ( event ) => {
 
 
-			dispatchEvent( 'register', event.detail );
+			dispatchEvent( EVENT_REGISTER, event.detail );
 
 
 		} );
 		} );
 
 
 		// Listen for object observations
 		// Listen for object observations
-		devTools.addEventListener( 'observe', ( event ) => {
+		devTools.addEventListener( EVENT_OBSERVE, ( event ) => {
 
 
 			const obj = event.detail;
 			const obj = event.detail;
 			if ( ! obj ) {
 			if ( ! obj ) {
@@ -229,7 +270,7 @@
 					observedRenderers.push( obj );
 					observedRenderers.push( obj );
 					devTools.objects.set( obj.uuid, data );
 					devTools.objects.set( obj.uuid, data );
 
 
-					dispatchEvent( 'renderer', data );
+					dispatchEvent( EVENT_RENDERER, data );
 
 
 				}
 				}
 
 
@@ -238,12 +279,8 @@
 				observedScenes.push( obj );
 				observedScenes.push( obj );
 
 
 				const batchObjects = [];
 				const batchObjects = [];
-				const processedUUIDs = new Set();
-
-				function traverseForBatch( currentObj ) {
 
 
-					if ( ! currentObj || ! currentObj.uuid || processedUUIDs.has( currentObj.uuid ) ) return;
-					processedUUIDs.add( currentObj.uuid );
+				traverseObjectTree( obj, ( currentObj ) => {
 
 
 					const objectData = getObjectData( currentObj );
 					const objectData = getObjectData( currentObj );
 					if ( objectData ) {
 					if ( objectData ) {
@@ -253,18 +290,9 @@
 
 
 					}
 					}
 
 
-					// Process children
-					if ( currentObj.children && Array.isArray( currentObj.children ) ) {
+				}, true );
 
 
-						currentObj.children.forEach( child => traverseForBatch( child ) );
-
-					}
-
-				}
-
-				traverseForBatch( obj ); // Start traversal from the scene
-
-				dispatchEvent( 'scene', { sceneUuid: obj.uuid, objects: batchObjects } );
+				dispatchEvent( EVENT_SCENE, { sceneUuid: obj.uuid, objects: batchObjects } );
 
 
 			}
 			}
 
 
@@ -316,13 +344,12 @@
 		// Function to check if bridge is available
 		// Function to check if bridge is available
 		function checkBridgeAvailability() {
 		function checkBridgeAvailability() {
 
 
-			const hasDevTools = window.hasOwnProperty( '__THREE_DEVTOOLS__' );
 			const devToolsValue = window.__THREE_DEVTOOLS__;
 			const devToolsValue = window.__THREE_DEVTOOLS__;
 
 
 			// If we have devtools and we're interactive or complete, trigger ready
 			// If we have devtools and we're interactive or complete, trigger ready
-			if ( hasDevTools && devToolsValue && ( document.readyState === 'interactive' || document.readyState === 'complete' ) ) {
+			if ( devToolsValue && ( document.readyState === 'interactive' || document.readyState === 'complete' ) ) {
 
 
-				devTools.dispatchEvent( new CustomEvent( 'devtools-ready' ) );
+				devTools.dispatchEvent( new CustomEvent( EVENT_DEVTOOLS_READY ) );
 
 
 			}
 			}
 
 
@@ -346,7 +373,7 @@
 
 
 			if ( window.THREE && window.THREE.REVISION ) {
 			if ( window.THREE && window.THREE.REVISION ) {
 
 
-				dispatchEvent( 'register', { revision: THREE.REVISION } );
+				dispatchEvent( EVENT_REGISTER, { revision: THREE.REVISION } );
 
 
 			}
 			}
 
 
@@ -366,18 +393,18 @@
 			if ( event.source !== window ) return;
 			if ( event.source !== window ) return;
 
 
 			const message = event.data;
 			const message = event.data;
-			if ( ! message || message.id !== 'three-devtools' ) return;
+			if ( ! message || message.id !== MESSAGE_ID ) return;
 
 
 			// Handle request for initial state from panel
 			// Handle request for initial state from panel
-			if ( message.name === 'request-state' ) {
+			if ( message.name === MESSAGE_REQUEST_STATE ) {
 
 
 				sendState();
 				sendState();
 
 
-			} else if ( message.name === 'request-object-details' ) {
+			} else if ( message.name === MESSAGE_REQUEST_OBJECT_DETAILS ) {
 
 
 				sendObjectDetails( message.uuid );
 				sendObjectDetails( message.uuid );
 
 
-			} else if ( message.name === 'scroll-to-canvas' ) {
+			} else if ( message.name === MESSAGE_SCROLL_TO_CANVAS ) {
 
 
 				scrollToCanvas( message.uuid );
 				scrollToCanvas( message.uuid );
 
 
@@ -394,7 +421,7 @@
 				if ( data ) {
 				if ( data ) {
 
 
 					data.properties = getRendererProperties( observedRenderer );
 					data.properties = getRendererProperties( observedRenderer );
-					dispatchEvent( 'renderer', data );
+					dispatchEvent( EVENT_RENDERER, data );
 
 
 				}
 				}
 
 
@@ -422,6 +449,44 @@
 
 
 		}
 		}
 
 
+		function createHighlightOverlay( targetElement ) {
+
+			const overlay = document.createElement( 'div' );
+			overlay.style.cssText = `
+				position: absolute;
+				top: 0;
+				left: 0;
+				width: 100%;
+				height: 100%;
+				background-color: rgba(0, 122, 204, 0.3);
+				pointer-events: none;
+				z-index: 999999;
+			`;
+
+			// Position the overlay relative to the target
+			const parent = targetElement.parentElement || document.body;
+
+			if ( getComputedStyle( parent ).position === 'static' ) {
+
+				parent.style.position = 'relative';
+
+			}
+
+			parent.appendChild( overlay );
+
+			// Auto-remove after 1 second
+			setTimeout( () => {
+
+				if ( overlay.parentElement ) {
+
+					overlay.parentElement.removeChild( overlay );
+
+				}
+
+			}, 1000 );
+
+		}
+
 		function sendObjectDetails( uuid ) {
 		function sendObjectDetails( uuid ) {
 
 
 			const object = findObjectInScenes( uuid );
 			const object = findObjectInScenes( uuid );
@@ -449,7 +514,7 @@
 					}
 					}
 				};
 				};
 
 
-				dispatchEvent( 'object-details', details );
+				dispatchEvent( EVENT_OBJECT_DETAILS, details );
 
 
 			}
 			}
 
 
@@ -458,15 +523,19 @@
 		function scrollToCanvas( uuid ) {
 		function scrollToCanvas( uuid ) {
 
 
 			let renderer = null;
 			let renderer = null;
-			
+
 			if ( uuid ) {
 			if ( uuid ) {
+
 				// Find the renderer with the given UUID
 				// Find the renderer with the given UUID
 				renderer = observedRenderers.find( r => r.uuid === uuid );
 				renderer = observedRenderers.find( r => r.uuid === uuid );
+
 			} else {
 			} else {
+
 				// If no UUID provided, find the first available renderer whose canvas is in the DOM
 				// If no UUID provided, find the first available renderer whose canvas is in the DOM
 				renderer = observedRenderers.find( r => r.domElement && document.body.contains( r.domElement ) );
 				renderer = observedRenderers.find( r => r.domElement && document.body.contains( r.domElement ) );
+
 			}
 			}
-			
+
 			if ( renderer ) {
 			if ( renderer ) {
 
 
 				// Scroll the canvas element into view
 				// Scroll the canvas element into view
@@ -477,32 +546,7 @@
 				} );
 				} );
 
 
 				// Add a brief blue overlay flash effect
 				// Add a brief blue overlay flash effect
-				const overlay = document.createElement('div');
-				overlay.style.cssText = `
-					position: absolute;
-					top: 0;
-					left: 0;
-					width: 100%;
-					height: 100%;
-					background-color: rgba(0, 122, 204, 0.3);
-					pointer-events: none;
-					z-index: 999999;
-				`;
-				
-				// Position the overlay relative to the canvas
-				const canvasParent = renderer.domElement.parentElement || document.body;
-				
-				if (getComputedStyle(canvasParent).position === 'static') {
-					canvasParent.style.position = 'relative';
-				}
-				
-				canvasParent.appendChild(overlay);
-				
-				setTimeout(() => {
-					if (overlay.parentElement) {
-						overlay.parentElement.removeChild(overlay);
-					}
-				}, 1000);
+				createHighlightOverlay( renderer.domElement );
 
 
 			}
 			}
 
 
@@ -513,7 +557,7 @@
 			try {
 			try {
 
 
 				window.postMessage( {
 				window.postMessage( {
-					id: 'three-devtools',
+					id: MESSAGE_ID,
 					name: name,
 					name: name,
 					detail: detail
 					detail: detail
 				}, '*' );
 				}, '*' );
@@ -540,13 +584,8 @@
 
 
 			const batchObjects = [];
 			const batchObjects = [];
 
 
-			// Recursively observe all objects, collect data, update local cache
-			function observeAndBatchObject( object ) {
+			traverseObjectTree( scene, ( object ) => {
 
 
-				if ( ! object || ! object.uuid ) return; // Simplified check
-
-
-				// Get object data
 				const objectData = getObjectData( object );
 				const objectData = getObjectData( object );
 				if ( objectData ) {
 				if ( objectData ) {
 
 
@@ -556,17 +595,7 @@
 
 
 				}
 				}
 
 
-				// Process children recursively
-				if ( object.children && Array.isArray( object.children ) ) {
-
-					object.children.forEach( child => observeAndBatchObject( child ) );
-
-				}
-
-			}
-
-			// Start traversal from the scene itself
-			observeAndBatchObject( scene );
+			} );
 
 
 			// --- Caching Logic ---
 			// --- Caching Logic ---
 			const currentObjectCount = batchObjects.length;
 			const currentObjectCount = batchObjects.length;
@@ -575,8 +604,8 @@
 			if ( currentObjectCount !== previousObjectCount ) {
 			if ( currentObjectCount !== previousObjectCount ) {
 
 
 				console.log( `DevTools: Scene ${scene.uuid} count changed (${previousObjectCount} -> ${currentObjectCount}), dispatching update.` );
 				console.log( `DevTools: Scene ${scene.uuid} count changed (${previousObjectCount} -> ${currentObjectCount}), dispatching update.` );
-				// Dispatch the batch update for the panel as 'scene'
-				dispatchEvent( 'scene', { sceneUuid: scene.uuid, objects: batchObjects } );
+				// Dispatch the batch update for the panel
+				dispatchEvent( EVENT_SCENE, { sceneUuid: scene.uuid, objects: batchObjects } );
 				// Update the cache
 				// Update the cache
 				sceneObjectCountCache.set( scene.uuid, currentObjectCount );
 				sceneObjectCountCache.set( scene.uuid, currentObjectCount );
 
 

+ 20 - 14
devtools/content-script.js

@@ -2,6 +2,12 @@
 
 
 // This script runs in the context of the web page
 // This script runs in the context of the web page
 
 
+// Constants
+const MESSAGE_ID = 'three-devtools';
+const MESSAGE_REQUEST_STATE = 'request-state';
+const MESSAGE_REQUEST_OBJECT_DETAILS = 'request-object-details';
+const MESSAGE_SCROLL_TO_CANVAS = 'scroll-to-canvas';
+
 // Inject the bridge script into the main document or a target (e.g., iframe)
 // Inject the bridge script into the main document or a target (e.g., iframe)
 function injectBridge( target = document ) {
 function injectBridge( target = document ) {
 
 
@@ -27,7 +33,11 @@ function injectIntoIframes() {
 
 
 			if ( iframe.contentDocument ) injectBridge( iframe.contentDocument );
 			if ( iframe.contentDocument ) injectBridge( iframe.contentDocument );
 
 
-		} catch ( e ) { /* Ignore cross-origin errors */ }
+		} catch ( e ) {
+
+			// Ignore cross-origin errors when accessing iframe content
+
+		}
 
 
 	} );
 	} );
 
 
@@ -52,7 +62,11 @@ new MutationObserver( mutations => {
 
 
 						if ( node.contentDocument ) injectBridge( node.contentDocument );
 						if ( node.contentDocument ) injectBridge( node.contentDocument );
 
 
-					} catch ( e ) { /* Ignore cross-origin errors */ }
+					} catch ( e ) {
+
+						// Ignore cross-origin errors when accessing iframe content
+
+					}
 
 
 				} );
 				} );
 
 
@@ -84,7 +98,7 @@ function isExtensionContextValid() {
 function handleWindowMessage( event ) {
 function handleWindowMessage( event ) {
 
 
 	// Only accept messages with the correct id
 	// Only accept messages with the correct id
-	if ( ! event.data || event.data.id !== 'three-devtools' ) return;
+	if ( ! event.data || event.data.id !== MESSAGE_ID ) return;
 
 
 	// Determine source: 'main' for window, 'iframe' otherwise
 	// Determine source: 'main' for window, 'iframe' otherwise
 	const source = event.source === window ? 'main' : 'iframe';
 	const source = event.source === window ? 'main' : 'iframe';
@@ -104,19 +118,11 @@ function handleWindowMessage( event ) {
 // Listener for messages from the background script (originating from panel)
 // Listener for messages from the background script (originating from panel)
 function handleBackgroundMessage( message ) {
 function handleBackgroundMessage( message ) {
 
 
-	if ( message.name === 'request-state' ) {
-
-		message.id = 'three-devtools';
-		window.postMessage( message, '*' );
-
-	} else if ( message.name === 'request-object-details' ) {
-
-		message.id = 'three-devtools';
-		window.postMessage( message, '*' );
+	const forwardableMessages = new Set( [ MESSAGE_REQUEST_STATE, MESSAGE_REQUEST_OBJECT_DETAILS, MESSAGE_SCROLL_TO_CANVAS ] );
 
 
-	} else if ( message.name === 'scroll-to-canvas' ) {
+	if ( forwardableMessages.has( message.name ) ) {
 
 
-		message.id = 'three-devtools';
+		message.id = MESSAGE_ID;
 		window.postMessage( message, '*' );
 		window.postMessage( message, '*' );
 
 
 	}
 	}

+ 1 - 1
devtools/manifest.json

@@ -1,7 +1,7 @@
 {
 {
 	"manifest_version": 3,
 	"manifest_version": 3,
 	"name": "Three.js DevTools",
 	"name": "Three.js DevTools",
-	"version": "1.11",
+	"version": "1.12",
 	"description": "Developer tools extension for Three.js",
 	"description": "Developer tools extension for Three.js",
 	"icons": {
 	"icons": {
 		"128": "icons/128-light.png"
 		"128": "icons/128-light.png"

+ 146 - 127
devtools/panel/panel.js

@@ -1,5 +1,17 @@
 /* global chrome */
 /* global chrome */
 
 
+// Constants
+const MESSAGE_ID = 'three-devtools';
+const MESSAGE_INIT = 'init';
+const MESSAGE_REQUEST_STATE = 'request-state';
+const MESSAGE_REQUEST_OBJECT_DETAILS = 'request-object-details';
+const MESSAGE_SCROLL_TO_CANVAS = 'scroll-to-canvas';
+const EVENT_REGISTER = 'register';
+const EVENT_RENDERER = 'renderer';
+const EVENT_OBJECT_DETAILS = 'object-details';
+const EVENT_SCENE = 'scene';
+const EVENT_COMMITTED = 'committed';
+
 // --- Utility Functions ---
 // --- Utility Functions ---
 function getObjectIcon(obj) {
 function getObjectIcon(obj) {
 	if (obj.isScene) return '🌍';
 	if (obj.isScene) return '🌍';
@@ -73,20 +85,20 @@ const backgroundPageConnection = chrome.runtime.connect( {
 
 
 // Initialize the connection with the inspected tab ID
 // Initialize the connection with the inspected tab ID
 backgroundPageConnection.postMessage( {
 backgroundPageConnection.postMessage( {
-	name: 'init',
+	name: MESSAGE_INIT,
 	tabId: chrome.devtools.inspectedWindow.tabId
 	tabId: chrome.devtools.inspectedWindow.tabId
 } );
 } );
 
 
 // Request the initial state from the bridge script
 // Request the initial state from the bridge script
 backgroundPageConnection.postMessage( {
 backgroundPageConnection.postMessage( {
-	name: 'request-state',
+	name: MESSAGE_REQUEST_STATE,
 	tabId: chrome.devtools.inspectedWindow.tabId
 	tabId: chrome.devtools.inspectedWindow.tabId
 } );
 } );
 
 
 // Function to scroll to canvas element
 // Function to scroll to canvas element
 function scrollToCanvas( rendererUuid ) {
 function scrollToCanvas( rendererUuid ) {
 	backgroundPageConnection.postMessage( {
 	backgroundPageConnection.postMessage( {
-		name: 'scroll-to-canvas',
+		name: MESSAGE_SCROLL_TO_CANVAS,
 		uuid: rendererUuid,
 		uuid: rendererUuid,
 		tabId: chrome.devtools.inspectedWindow.tabId
 		tabId: chrome.devtools.inspectedWindow.tabId
 	} );
 	} );
@@ -95,7 +107,7 @@ function scrollToCanvas( rendererUuid ) {
 const intervalId = setInterval( () => {
 const intervalId = setInterval( () => {
 
 
 	backgroundPageConnection.postMessage( {
 	backgroundPageConnection.postMessage( {
-		name: 'request-state',
+		name: MESSAGE_REQUEST_STATE,
 		tabId: chrome.devtools.inspectedWindow.tabId
 		tabId: chrome.devtools.inspectedWindow.tabId
 	} );
 	} );
 
 
@@ -112,7 +124,7 @@ backgroundPageConnection.onDisconnect.addListener( () => {
 // Function to request object details from the bridge
 // Function to request object details from the bridge
 function requestObjectDetails( uuid ) {
 function requestObjectDetails( uuid ) {
 	backgroundPageConnection.postMessage( {
 	backgroundPageConnection.postMessage( {
-		name: 'request-object-details',
+		name: MESSAGE_REQUEST_OBJECT_DETAILS,
 		uuid: uuid,
 		uuid: uuid,
 		tabId: chrome.devtools.inspectedWindow.tabId
 		tabId: chrome.devtools.inspectedWindow.tabId
 	} );
 	} );
@@ -122,6 +134,124 @@ function requestObjectDetails( uuid ) {
 // Store renderer collapse states
 // Store renderer collapse states
 const rendererCollapsedState = new Map();
 const rendererCollapsedState = new Map();
 
 
+// Helper function to create properties column for renderer
+function createRendererPropertiesColumn( props ) {
+
+	const propsCol = document.createElement( 'div' );
+	propsCol.className = 'properties-column';
+	const propsTitle = document.createElement( 'h4' );
+	propsTitle.textContent = 'Properties';
+	propsCol.appendChild( propsTitle );
+	propsCol.appendChild( createPropertyRow( 'Size', `${props.width}x${props.height}` ) );
+	propsCol.appendChild( createPropertyRow( 'Alpha', props.alpha ) );
+	propsCol.appendChild( createPropertyRow( 'Antialias', props.antialias ) );
+	propsCol.appendChild( createPropertyRow( 'Output Color Space', props.outputColorSpace ) );
+	propsCol.appendChild( createPropertyRow( 'Tone Mapping', props.toneMapping ) );
+	propsCol.appendChild( createPropertyRow( 'Tone Mapping Exposure', props.toneMappingExposure ) );
+	propsCol.appendChild( createPropertyRow( 'Shadows', props.shadows ? 'enabled' : 'disabled' ) );
+	propsCol.appendChild( createPropertyRow( 'Auto Clear', props.autoClear ) );
+	propsCol.appendChild( createPropertyRow( 'Auto Clear Color', props.autoClearColor ) );
+	propsCol.appendChild( createPropertyRow( 'Auto Clear Depth', props.autoClearDepth ) );
+	propsCol.appendChild( createPropertyRow( 'Auto Clear Stencil', props.autoClearStencil ) );
+	propsCol.appendChild( createPropertyRow( 'Local Clipping', props.localClipping ) );
+	propsCol.appendChild( createPropertyRow( 'Physically Correct Lights', props.physicallyCorrectLights ) );
+
+	return propsCol;
+
+}
+
+// Helper function to create stats column for renderer
+function createRendererStatsColumn( info ) {
+
+	const statsCol = document.createElement( 'div' );
+	statsCol.className = 'stats-column';
+
+	// Render Stats
+	const renderTitle = document.createElement( 'h4' );
+	renderTitle.textContent = 'Render Stats';
+	statsCol.appendChild( renderTitle );
+	statsCol.appendChild( createPropertyRow( 'Frame', info.render.frame ) );
+	statsCol.appendChild( createPropertyRow( 'Draw Calls', info.render.calls ) );
+	statsCol.appendChild( createPropertyRow( 'Triangles', info.render.triangles ) );
+	statsCol.appendChild( createPropertyRow( 'Points', info.render.points ) );
+	statsCol.appendChild( createPropertyRow( 'Lines', info.render.lines ) );
+
+	// Memory
+	const memoryTitle = document.createElement( 'h4' );
+	memoryTitle.textContent = 'Memory';
+	memoryTitle.style.marginTop = '10px';
+	statsCol.appendChild( memoryTitle );
+	statsCol.appendChild( createPropertyRow( 'Geometries', info.memory.geometries ) );
+	statsCol.appendChild( createPropertyRow( 'Textures', info.memory.textures ) );
+	statsCol.appendChild( createPropertyRow( 'Shader Programs', info.memory.programs ) );
+
+	return statsCol;
+
+}
+
+// Helper function to process scene batch updates
+function processSceneBatch( sceneUuid, batchObjects ) {
+
+	// 1. Identify UUIDs in the new batch
+	const newObjectUuids = new Set( batchObjects.map( obj => obj.uuid ) );
+
+	// 2. Identify current object UUIDs associated with this scene that are NOT renderers
+	const currentSceneObjectUuids = new Set();
+	state.objects.forEach( ( obj, uuid ) => {
+
+		// Use the _sceneUuid property we'll add below, or check if it's the scene root itself
+		if ( obj._sceneUuid === sceneUuid || uuid === sceneUuid ) {
+
+			currentSceneObjectUuids.add( uuid );
+
+		}
+
+	} );
+
+	// 3. Find UUIDs to remove (in current state for this scene, but not in the new batch)
+	const uuidsToRemove = new Set();
+	currentSceneObjectUuids.forEach( uuid => {
+
+		if ( ! newObjectUuids.has( uuid ) ) {
+
+			uuidsToRemove.add( uuid );
+
+		}
+
+	} );
+
+	// 4. Remove stale objects from state
+	uuidsToRemove.forEach( uuid => {
+
+		state.objects.delete( uuid );
+		// If a scene object itself was somehow removed (unlikely for root), clean up scenes map too
+		if ( state.scenes.has( uuid ) ) {
+
+			state.scenes.delete( uuid );
+
+		}
+
+	} );
+
+	// 5. Process the new batch: Add/Update objects and mark their scene association
+	batchObjects.forEach( objData => {
+
+		// Add a private property to track which scene this object belongs to
+		objData._sceneUuid = sceneUuid;
+		state.objects.set( objData.uuid, objData );
+
+		// Ensure the scene root is in the scenes map
+		if ( objData.isScene && objData.uuid === sceneUuid ) {
+
+			state.scenes.set( objData.uuid, objData );
+
+		}
+		// Note: Renderers are handled separately by 'renderer' events and shouldn't appear in scene batches.
+
+	} );
+
+}
+
 // Clear state when panel is reloaded
 // Clear state when panel is reloaded
 function clearState() {
 function clearState() {
 
 
@@ -148,7 +278,7 @@ function clearState() {
 // Listen for messages from the background page
 // Listen for messages from the background page
 backgroundPageConnection.onMessage.addListener( function ( message ) {
 backgroundPageConnection.onMessage.addListener( function ( message ) {
 
 
-	if ( message.id === 'three-devtools' ) {
+	if ( message.id === MESSAGE_ID ) {
 
 
 		handleThreeEvent( message );
 		handleThreeEvent( message );
 
 
@@ -160,103 +290,35 @@ function handleThreeEvent( message ) {
 
 
 	switch ( message.name ) {
 	switch ( message.name ) {
 
 
-		case 'register':
+		case EVENT_REGISTER:
 			state.revision = message.detail.revision;
 			state.revision = message.detail.revision;
 			break;
 			break;
 
 
 		// Handle individual renderer observation
 		// Handle individual renderer observation
-		case 'renderer':
+		case EVENT_RENDERER:
 			const detail = message.detail;
 			const detail = message.detail;
 
 
-			// Only store each unique object once
-			if ( ! state.objects.has( detail.uuid ) ) {
-
-				state.objects.set( detail.uuid, detail );
-				state.renderers.set( detail.uuid, detail );
-
-			}
-
-			// Update or add the renderer in the state map
-			state.renderers.set( detail.uuid, detail ); // Ensure the latest detail is always stored
-			// Also update the generic objects map if renderers are stored there too
+			// Update or add the renderer in the state maps (always use latest data)
+			state.renderers.set( detail.uuid, detail );
 			state.objects.set( detail.uuid, detail );
 			state.objects.set( detail.uuid, detail );
 
 
-
 			break;
 			break;
 
 
 		// Handle object details response
 		// Handle object details response
-		case 'object-details':
+		case EVENT_OBJECT_DETAILS:
 			state.selectedObject = message.detail;
 			state.selectedObject = message.detail;
 			console.log( 'Panel: Received object details:', message.detail );
 			console.log( 'Panel: Received object details:', message.detail );
 			showFloatingDetails( message.detail );
 			showFloatingDetails( message.detail );
 			break;
 			break;
 
 
 		// Handle a batch of objects for a specific scene
 		// Handle a batch of objects for a specific scene
-		case 'scene':
+		case EVENT_SCENE:
 			const { sceneUuid, objects: batchObjects } = message.detail;
 			const { sceneUuid, objects: batchObjects } = message.detail;
 			console.log( 'Panel: Received scene batch for', sceneUuid, 'with', batchObjects.length, 'objects' );
 			console.log( 'Panel: Received scene batch for', sceneUuid, 'with', batchObjects.length, 'objects' );
-
-			// 1. Identify UUIDs in the new batch
-			const newObjectUuids = new Set( batchObjects.map( obj => obj.uuid ) );
-
-			// 2. Identify current object UUIDs associated with this scene that are NOT renderers
-			const currentSceneObjectUuids = new Set();
-			state.objects.forEach( ( obj, uuid ) => {
-
-				// Use the _sceneUuid property we'll add below, or check if it's the scene root itself
-				if ( obj._sceneUuid === sceneUuid || uuid === sceneUuid ) {
-
-					currentSceneObjectUuids.add( uuid );
-
-				}
-
-			} );
-
-			// 3. Find UUIDs to remove (in current state for this scene, but not in the new batch)
-			const uuidsToRemove = new Set();
-			currentSceneObjectUuids.forEach( uuid => {
-
-				if ( ! newObjectUuids.has( uuid ) ) {
-
-					uuidsToRemove.add( uuid );
-
-				}
-
-			} );
-
-			// 4. Remove stale objects from state
-			uuidsToRemove.forEach( uuid => {
-
-				state.objects.delete( uuid );
-				// If a scene object itself was somehow removed (unlikely for root), clean up scenes map too
-				if ( state.scenes.has( uuid ) ) {
-
-					state.scenes.delete( uuid );
-
-				}
-
-			} );
-
-			// 5. Process the new batch: Add/Update objects and mark their scene association
-			batchObjects.forEach( objData => {
-
-				// Add a private property to track which scene this object belongs to
-				objData._sceneUuid = sceneUuid;
-				state.objects.set( objData.uuid, objData );
-
-				// Ensure the scene root is in the scenes map
-				if ( objData.isScene && objData.uuid === sceneUuid ) {
-
-					state.scenes.set( objData.uuid, objData );
-
-				}
-				// Note: Renderers are handled separately by 'renderer' events and shouldn't appear in scene batches.
-
-			} );
-
+			processSceneBatch( sceneUuid, batchObjects );
 			break;
 			break;
 
 
-		case 'committed':
+		case EVENT_COMMITTED:
 			// Page was reloaded, clear state
 			// Page was reloaded, clear state
 			clearState();
 			clearState();
 			break;
 			break;
@@ -328,51 +390,8 @@ function renderRenderer( obj, container ) {
 		gridContainer.style.gridTemplateColumns = 'repeat(auto-fit, minmax(200px, 1fr))'; // Responsive columns
 		gridContainer.style.gridTemplateColumns = 'repeat(auto-fit, minmax(200px, 1fr))'; // Responsive columns
 		gridContainer.style.gap = '10px 20px'; // Row and column gap
 		gridContainer.style.gap = '10px 20px'; // Row and column gap
 
 
-		// --- Column 1: Properties ---
-		const propsCol = document.createElement( 'div' );
-		propsCol.className = 'properties-column';
-		const propsTitle = document.createElement( 'h4' );
-		propsTitle.textContent = 'Properties';
-		propsCol.appendChild( propsTitle );
-		propsCol.appendChild( createPropertyRow( 'Size', `${props.width}x${props.height}` ) );
-		propsCol.appendChild( createPropertyRow( 'Alpha', props.alpha ) );
-		propsCol.appendChild( createPropertyRow( 'Antialias', props.antialias ) );
-		propsCol.appendChild( createPropertyRow( 'Output Color Space', props.outputColorSpace ) );
-		propsCol.appendChild( createPropertyRow( 'Tone Mapping', props.toneMapping ) );
-		propsCol.appendChild( createPropertyRow( 'Tone Mapping Exposure', props.toneMappingExposure ) );
-		propsCol.appendChild( createPropertyRow( 'Shadows', props.shadows ? 'enabled' : 'disabled' ) ); // Display string
-		propsCol.appendChild( createPropertyRow( 'Auto Clear', props.autoClear ) );
-		propsCol.appendChild( createPropertyRow( 'Auto Clear Color', props.autoClearColor ) );
-		propsCol.appendChild( createPropertyRow( 'Auto Clear Depth', props.autoClearDepth ) );
-		propsCol.appendChild( createPropertyRow( 'Auto Clear Stencil', props.autoClearStencil ) );
-		propsCol.appendChild( createPropertyRow( 'Local Clipping', props.localClipping ) );
-		propsCol.appendChild( createPropertyRow( 'Physically Correct Lights', props.physicallyCorrectLights ) );
-		gridContainer.appendChild( propsCol );
-
-		// --- Column 2: Render Stats & Memory ---
-		const statsCol = document.createElement( 'div' );
-		statsCol.className = 'stats-column';
-
-		// Render Stats
-		const renderTitle = document.createElement( 'h4' );
-		renderTitle.textContent = 'Render Stats';
-		statsCol.appendChild( renderTitle );
-		statsCol.appendChild( createPropertyRow( 'Frame', info.render.frame ) );
-		statsCol.appendChild( createPropertyRow( 'Draw Calls', info.render.calls ) );
-		statsCol.appendChild( createPropertyRow( 'Triangles', info.render.triangles ) );
-		statsCol.appendChild( createPropertyRow( 'Points', info.render.points ) );
-		statsCol.appendChild( createPropertyRow( 'Lines', info.render.lines ) );
-
-		// Memory
-		const memoryTitle = document.createElement( 'h4' );
-		memoryTitle.textContent = 'Memory';
-		memoryTitle.style.marginTop = '10px'; // Add space before Memory section
-		statsCol.appendChild( memoryTitle );
-		statsCol.appendChild( createPropertyRow( 'Geometries', info.memory.geometries ) ); // Memory Geometries
-		statsCol.appendChild( createPropertyRow( 'Textures', info.memory.textures ) );
-		statsCol.appendChild( createPropertyRow( 'Shader Programs', info.memory.programs ) );
-
-		gridContainer.appendChild( statsCol );
+		gridContainer.appendChild( createRendererPropertiesColumn( props ) );
+		gridContainer.appendChild( createRendererStatsColumn( info ) );
 		propsContainer.appendChild( gridContainer );
 		propsContainer.appendChild( gridContainer );
 
 
 	} else {
 	} else {

粤ICP备19079148号