bridge.js 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670
  1. /**
  2. * This script injected by the installed three.js developer
  3. * tools extension.
  4. */
  5. // Only initialize if not already initialized
  6. if (!window.__THREE_DEVTOOLS__) {
  7. // Create our custom EventTarget with logging
  8. class DevToolsEventTarget extends EventTarget {
  9. constructor() {
  10. super();
  11. this._ready = false;
  12. this._backlog = [];
  13. this.objects = new Map();
  14. // console.log('DevTools: Creating ThreeDevToolsTarget');
  15. }
  16. addEventListener(type, listener, options) {
  17. // console.log('DevTools: Adding listener for:', type);
  18. super.addEventListener(type, listener, options);
  19. // If this is the first listener for a type, and we have backlogged events,
  20. // check if we should process them
  21. if (type !== 'devtools-ready' && this._backlog.length > 0) {
  22. this.dispatchEvent(new CustomEvent('devtools-ready'));
  23. }
  24. }
  25. dispatchEvent(event) {
  26. console.log('DevTools: Dispatching event:', event.type);
  27. if (this._ready || event.type === 'devtools-ready') {
  28. if (event.type === 'devtools-ready') {
  29. this._ready = true;
  30. console.log('DevTools: Processing backlog:', this._backlog.length, 'events');
  31. const backlog = this._backlog;
  32. this._backlog = [];
  33. backlog.forEach(e => super.dispatchEvent(e));
  34. }
  35. return super.dispatchEvent(event);
  36. } else {
  37. console.log('DevTools: Backlogging event:', event.type);
  38. this._backlog.push(event);
  39. return false; // Return false to indicate synchronous handling
  40. }
  41. }
  42. reset() {
  43. console.log('DevTools: Resetting state');
  44. // Clear all monitoring intervals
  45. this.objects.forEach((obj, uuid) => {
  46. if (obj.isRenderer || obj.isScene) {
  47. const interval = monitoringIntervals.get(obj);
  48. if (interval) {
  49. clearInterval(interval);
  50. monitoringIntervals.delete(obj);
  51. }
  52. }
  53. });
  54. // Clear objects map
  55. this.objects.clear();
  56. // Clear backlog
  57. this._backlog = [];
  58. // Reset ready state
  59. this._ready = false;
  60. // Clear observed arrays
  61. observedScenes.length = 0;
  62. observedRenderers.length = 0;
  63. }
  64. }
  65. // Create and expose the __THREE_DEVTOOLS__ object
  66. const devTools = new DevToolsEventTarget();
  67. Object.defineProperty(window, '__THREE_DEVTOOLS__', {
  68. value: devTools,
  69. configurable: false,
  70. enumerable: true,
  71. writable: false
  72. });
  73. // Store monitoring intervals without polluting objects
  74. const monitoringIntervals = new WeakMap();
  75. // Declare arrays for tracking observed objects
  76. const observedScenes = [];
  77. const observedRenderers = [];
  78. // Function to get renderer data
  79. function getRendererData(renderer) {
  80. try {
  81. const webglInfo = getWebGLInfo(renderer);
  82. const data = {
  83. uuid: renderer.uuid || generateUUID(),
  84. type: 'WebGLRenderer',
  85. name: '',
  86. visible: true,
  87. isScene: false,
  88. isObject3D: false,
  89. isCamera: false,
  90. isLight: false,
  91. isMesh: false,
  92. isRenderer: true,
  93. parent: null,
  94. children: [],
  95. properties: {
  96. width: renderer.domElement ? renderer.domElement.clientWidth : 0,
  97. height: renderer.domElement ? renderer.domElement.clientHeight : 0,
  98. drawingBufferWidth: renderer.domElement ? renderer.domElement.width : 0,
  99. drawingBufferHeight: renderer.domElement ? renderer.domElement.height : 0,
  100. alpha: renderer.alpha || false,
  101. antialias: renderer.antialias || false,
  102. autoClear: renderer.autoClear,
  103. autoClearColor: renderer.autoClearColor,
  104. autoClearDepth: renderer.autoClearDepth,
  105. autoClearStencil: renderer.autoClearStencil,
  106. localClippingEnabled: renderer.localClippingEnabled,
  107. physicallyCorrectLights: renderer.physicallyCorrectLights,
  108. outputColorSpace: renderer.outputColorSpace,
  109. toneMapping: renderer.toneMapping,
  110. toneMappingExposure: renderer.toneMappingExposure,
  111. shadowMapEnabled: renderer.shadowMap ? renderer.shadowMap.enabled : false,
  112. shadowMapType: renderer.shadowMap ? renderer.shadowMap.type : 'None',
  113. info: {
  114. render: {
  115. frame: renderer.info.render.frame,
  116. calls: renderer.info.render.calls,
  117. triangles: renderer.info.render.triangles,
  118. points: renderer.info.render.points,
  119. lines: renderer.info.render.lines,
  120. geometries: renderer.info.render.geometries,
  121. sprites: renderer.info.render.sprites
  122. },
  123. memory: {
  124. geometries: renderer.info.memory.geometries,
  125. textures: renderer.info.memory.textures,
  126. programs: renderer.info.programs ? renderer.info.programs.length : 0,
  127. renderLists: renderer.info.memory.renderLists,
  128. renderTargets: renderer.info.memory.renderTargets
  129. },
  130. webgl: webglInfo || {
  131. version: 'unknown',
  132. gpu: 'unknown',
  133. vendor: 'unknown',
  134. maxTextures: 'unknown',
  135. maxAttributes: 'unknown',
  136. maxTextureSize: 'unknown',
  137. maxCubemapSize: 'unknown'
  138. }
  139. }
  140. }
  141. };
  142. return data;
  143. } catch (error) {
  144. console.warn('DevTools: Error getting renderer data:', error);
  145. return null;
  146. }
  147. }
  148. // Function to get object hierarchy
  149. function getObjectData(obj) {
  150. try {
  151. // Special case for WebGLRenderer
  152. if (obj.isWebGLRenderer === true) {
  153. return getRendererData(obj);
  154. }
  155. // Get descriptive name for the object
  156. let descriptiveName;
  157. if (obj.isMesh) {
  158. const geoType = obj.geometry ? obj.geometry.type : 'Unknown';
  159. const matType = obj.material ?
  160. (Array.isArray(obj.material) ?
  161. obj.material.map(m => m.type).join(', ') :
  162. obj.material.type) :
  163. 'Unknown';
  164. descriptiveName = `${obj.type || 'Mesh'} <span class="object-details">${geoType} ${matType}</span>`;
  165. } else if (obj.isLight) {
  166. descriptiveName = `${obj.type || 'Light'}`;
  167. } else if (obj.isCamera) {
  168. descriptiveName = `${obj.type || 'Camera'}`;
  169. } else {
  170. descriptiveName = obj.type || obj.constructor.name;
  171. }
  172. const data = {
  173. uuid: obj.uuid,
  174. type: obj.type || obj.constructor.name,
  175. name: descriptiveName,
  176. visible: obj.visible !== undefined ? obj.visible : true,
  177. isScene: obj.isScene === true,
  178. isObject3D: obj.isObject3D === true,
  179. isCamera: obj.isCamera === true,
  180. isLight: obj.isLight === true,
  181. isMesh: obj.isMesh === true,
  182. isRenderer: obj.isWebGLRenderer === true,
  183. parent: obj.parent ? obj.parent.uuid : null,
  184. children: obj.children ? obj.children.map(child => child.uuid) : []
  185. };
  186. // console.log('DevTools: Object data:', data);
  187. return data;
  188. } catch (error) {
  189. console.warn('DevTools: Error getting object data:', error);
  190. return null;
  191. }
  192. }
  193. // Generate a UUID for objects that don't have one
  194. function generateUUID() {
  195. return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
  196. const r = Math.random() * 16 | 0;
  197. const v = c === 'x' ? r : (r & 0x3 | 0x8);
  198. return v.toString(16);
  199. });
  200. }
  201. // Listen for Three.js registration
  202. devTools.addEventListener('register', (event) => {
  203. console.log('DevTools: Three.js registered with revision:', event.detail.revision);
  204. dispatchEvent('register', event.detail);
  205. });
  206. // Listen for object observations
  207. devTools.addEventListener('observe', (event) => {
  208. const obj = event.detail;
  209. if (!obj) {
  210. console.warn('DevTools: Received observe event with null/undefined detail');
  211. return;
  212. }
  213. console.log('DevTools: Received object:', {
  214. type: obj.type || obj.constructor.name,
  215. isWebGLRenderer: obj.isWebGLRenderer === true,
  216. hasUUID: !!obj.uuid
  217. });
  218. // Generate UUID if needed (especially for WebGLRenderer)
  219. if (!obj.uuid) {
  220. obj.uuid = generateUUID();
  221. console.log('DevTools: Generated UUID for object:', obj.uuid);
  222. }
  223. // Skip if already registered
  224. if (devTools.objects.has(obj.uuid)) {
  225. console.log('DevTools: Object already registered:', obj.uuid);
  226. return;
  227. }
  228. console.log('DevTools: Found Three.js object:', obj.type || obj.constructor.name);
  229. // Get data for this object
  230. const data = getObjectData(obj);
  231. if (data) {
  232. console.log('DevTools: Got object data:', data);
  233. // If this is a renderer, start periodic updates
  234. if (obj.isWebGLRenderer) {
  235. console.log('DevTools: Starting periodic updates for renderer:', obj.uuid);
  236. data.properties = getRendererProperties(obj);
  237. observedRenderers.push(obj);
  238. startRendererMonitoring(obj);
  239. }
  240. // Store the object data
  241. devTools.objects.set(obj.uuid, data);
  242. dispatchEvent('observe', data);
  243. // If this is a scene, store the reference and traverse its children
  244. if (obj.isScene) {
  245. console.log('DevTools: Traversing scene children');
  246. // Store the scene reference locally
  247. observedScenes.push(obj);
  248. // First observe all existing children
  249. const processedObjects = new Set([obj.uuid]);
  250. function observeObject(object) {
  251. if (!processedObjects.has(object.uuid)) {
  252. processedObjects.add(object.uuid);
  253. const objectData = getObjectData(object);
  254. if (objectData) {
  255. devTools.objects.set(object.uuid, objectData);
  256. dispatchEvent('observe', objectData);
  257. }
  258. // Process children
  259. object.children.forEach(child => observeObject(child));
  260. }
  261. }
  262. // Process all children
  263. obj.children.forEach(child => observeObject(child));
  264. // Start monitoring for changes
  265. startSceneMonitoring(obj);
  266. }
  267. }
  268. });
  269. // Function to get renderer properties
  270. function getRendererProperties(renderer) {
  271. const webglInfo = getWebGLInfo(renderer);
  272. return {
  273. width: renderer.domElement ? renderer.domElement.clientWidth : 0,
  274. height: renderer.domElement ? renderer.domElement.clientHeight : 0,
  275. drawingBufferWidth: renderer.domElement ? renderer.domElement.width : 0,
  276. drawingBufferHeight: renderer.domElement ? renderer.domElement.height : 0,
  277. alpha: renderer.alpha || false,
  278. antialias: renderer.antialias || false,
  279. autoClear: renderer.autoClear,
  280. autoClearColor: renderer.autoClearColor,
  281. autoClearDepth: renderer.autoClearDepth,
  282. autoClearStencil: renderer.autoClearStencil,
  283. localClippingEnabled: renderer.localClippingEnabled,
  284. physicallyCorrectLights: renderer.physicallyCorrectLights,
  285. outputColorSpace: renderer.outputColorSpace,
  286. toneMapping: renderer.toneMapping,
  287. toneMappingExposure: renderer.toneMappingExposure,
  288. shadowMapEnabled: renderer.shadowMap ? renderer.shadowMap.enabled : false,
  289. shadowMapType: renderer.shadowMap ? renderer.shadowMap.type : 'None',
  290. info: {
  291. render: {
  292. frame: renderer.info.render.frame,
  293. calls: renderer.info.render.calls,
  294. triangles: renderer.info.render.triangles,
  295. points: renderer.info.render.points,
  296. lines: renderer.info.render.lines,
  297. geometries: renderer.info.render.geometries,
  298. sprites: renderer.info.render.sprites
  299. },
  300. memory: {
  301. geometries: renderer.info.memory.geometries,
  302. textures: renderer.info.memory.textures,
  303. programs: renderer.info.programs ? renderer.info.programs.length : 0,
  304. renderLists: renderer.info.memory.renderLists,
  305. renderTargets: renderer.info.memory.renderTargets
  306. },
  307. webgl: webglInfo || {
  308. version: 'unknown',
  309. gpu: 'unknown',
  310. vendor: 'unknown',
  311. maxTextures: 'unknown',
  312. maxAttributes: 'unknown',
  313. maxTextureSize: 'unknown',
  314. maxCubemapSize: 'unknown'
  315. }
  316. }
  317. };
  318. }
  319. // Function to start renderer monitoring
  320. function startRendererMonitoring(renderer) {
  321. // Clear any existing monitoring
  322. const existingInterval = monitoringIntervals.get(renderer);
  323. if (existingInterval) {
  324. clearInterval(existingInterval);
  325. }
  326. // Function to monitor renderer properties
  327. function monitorRendererProperties() {
  328. try {
  329. // Skip updates if devtools is not visible
  330. if ( ! devTools.isVisible ) {
  331. return;
  332. }
  333. const data = devTools.objects.get( renderer.uuid );
  334. if ( ! data ) {
  335. clearInterval( intervalId );
  336. monitoringIntervals.delete( renderer );
  337. return;
  338. }
  339. const newProperties = getRendererProperties( renderer );
  340. if ( JSON.stringify( data.properties ) !== JSON.stringify( newProperties ) ) {
  341. data.properties = newProperties;
  342. dispatchEvent( 'update', data );
  343. }
  344. } catch ( error ) {
  345. // If we get an "Extension context invalidated" error, stop monitoring
  346. if ( error.message.includes( 'Extension context invalidated' ) ) {
  347. clearInterval( intervalId );
  348. monitoringIntervals.delete( renderer );
  349. devTools.reset();
  350. return;
  351. }
  352. console.warn( 'DevTools: Error in renderer monitoring:', error );
  353. }
  354. }
  355. const intervalId = setInterval(monitorRendererProperties, 1000);
  356. monitoringIntervals.set(renderer, intervalId);
  357. }
  358. // Start periodic renderer checks
  359. console.log('DevTools: Starting periodic renderer checks');
  360. // Function to check if bridge is available
  361. function checkBridgeAvailability() {
  362. const hasDevTools = window.hasOwnProperty('__THREE_DEVTOOLS__');
  363. const devToolsValue = window.__THREE_DEVTOOLS__;
  364. // If we have devtools and we're interactive or complete, trigger ready
  365. if (hasDevTools && devToolsValue && (document.readyState === 'interactive' || document.readyState === 'complete')) {
  366. devTools.dispatchEvent(new CustomEvent('devtools-ready'));
  367. }
  368. }
  369. // Watch for readyState changes
  370. document.addEventListener('readystatechange', () => {
  371. // console.log('DevTools: Document readyState changed to:', document.readyState);
  372. if (document.readyState === 'loading') {
  373. devTools.reset();
  374. }
  375. checkBridgeAvailability();
  376. });
  377. // Watch for page unload to reset state
  378. window.addEventListener('beforeunload', () => {
  379. devTools.reset();
  380. });
  381. // Listen for messages from the content script
  382. window.addEventListener('message', function(event) {
  383. // Only accept messages from the same frame
  384. if (event.source !== window) return;
  385. const message = event.data;
  386. if (!message || message.id !== 'three-devtools') return;
  387. // Handle traverse request
  388. if (message.name === 'traverse' && message.uuid) {
  389. // Find the scene in our objects
  390. const scene = Array.from(devTools.objects.values())
  391. .find(obj => obj.uuid === message.uuid && obj.isScene);
  392. if (scene) {
  393. console.log('DevTools: Re-traversing scene:', scene.uuid);
  394. // Find the actual scene object in the page
  395. const actualScene = findObjectByUUID(message.uuid);
  396. if (actualScene) {
  397. reloadSceneObjects(actualScene);
  398. }
  399. }
  400. }
  401. // Handle reload-scene request
  402. else if (message.name === 'reload-scene' && message.uuid) {
  403. console.log('DevTools: Received reload request for scene:', message.uuid);
  404. const actualScene = findObjectByUUID(message.uuid);
  405. if (actualScene) {
  406. reloadSceneObjects(actualScene);
  407. } else {
  408. console.warn('DevTools: Could not find scene for reload:', message.uuid);
  409. }
  410. }
  411. // Handle visibility toggle
  412. else if (message.name === 'visibility' && message.uuid !== undefined) {
  413. toggleVisibility(message.uuid, message.visible);
  414. }
  415. });
  416. // Helper function to find a Three.js object by UUID
  417. function findObjectByUUID(uuid) {
  418. console.log('DevTools: Finding object by UUID:', uuid);
  419. // Check for scenes we've observed
  420. const sceneData = Array.from(devTools.objects.values())
  421. .find(obj => obj.uuid === uuid && obj.isScene);
  422. if (sceneData) {
  423. // For scenes accessed through observe events, they are already available
  424. // through the scene object reference passed to the observe handler
  425. for (const observedScene of observedScenes) {
  426. if (observedScene && observedScene.uuid === uuid) {
  427. console.log('DevTools: Found scene in observed scenes');
  428. return observedScene;
  429. }
  430. }
  431. }
  432. console.warn('DevTools: Could not find object with UUID:', uuid);
  433. return null;
  434. }
  435. function dispatchEvent(type, detail) {
  436. try {
  437. window.postMessage({
  438. id: 'three-devtools',
  439. type: type,
  440. detail: detail
  441. }, '*');
  442. } catch (error) {
  443. // If we get an "Extension context invalidated" error, stop all monitoring
  444. if (error.message.includes('Extension context invalidated')) {
  445. console.log('DevTools: Extension context invalidated, stopping monitoring');
  446. devTools.reset();
  447. return;
  448. }
  449. console.warn('DevTools: Error dispatching event:', error);
  450. }
  451. }
  452. function getWebGLInfo(renderer) {
  453. if (!renderer || !renderer.domElement) return null;
  454. const gl = renderer.domElement.getContext('webgl2') || renderer.domElement.getContext('webgl');
  455. if (!gl) return null;
  456. return {
  457. version: gl.getParameter(gl.VERSION),
  458. gpu: gl.getParameter(gl.RENDERER),
  459. vendor: gl.getParameter(gl.VENDOR),
  460. maxTextures: gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS),
  461. maxAttributes: gl.getParameter(gl.MAX_VERTEX_ATTRIBS),
  462. maxTextureSize: gl.getParameter(gl.MAX_TEXTURE_SIZE),
  463. maxCubemapSize: gl.getParameter(gl.MAX_CUBE_MAP_TEXTURE_SIZE)
  464. };
  465. }
  466. // Add visibility toggle function
  467. function toggleVisibility(uuid, visible) {
  468. // Update our local state
  469. const obj = devTools.objects.get(uuid);
  470. if (!obj) return;
  471. obj.visible = visible;
  472. console.log('DevTools: Setting visibility of', obj.type || obj.constructor.name, 'to', visible);
  473. // Find the actual Three.js object using our observed scenes
  474. if (observedScenes.length > 0) {
  475. for (const scene of observedScenes) {
  476. let found = false;
  477. scene.traverse((object) => {
  478. if (object.uuid === uuid) {
  479. object.visible = visible;
  480. // If it's a light, update its helper visibility too
  481. if (object.isLight && object.helper) {
  482. object.helper.visible = visible;
  483. }
  484. found = true;
  485. console.log('DevTools: Updated visibility in scene object');
  486. }
  487. });
  488. if (found) break;
  489. }
  490. } else {
  491. console.warn('DevTools: No observed scenes found for visibility toggle');
  492. }
  493. }
  494. // Function to start scene monitoring
  495. function startSceneMonitoring(scene) {
  496. // Clear any existing monitoring
  497. const existingInterval = monitoringIntervals.get(scene);
  498. if (existingInterval) {
  499. clearInterval(existingInterval);
  500. }
  501. // Set up monitoring interval
  502. const intervalId = setInterval(() => {
  503. try {
  504. // Clear existing objects except renderers and the scene itself
  505. devTools.objects.forEach((obj, uuid) => {
  506. if (!obj.isRenderer && uuid !== scene.uuid) {
  507. devTools.objects.delete(uuid);
  508. dispatchEvent('remove', { uuid });
  509. }
  510. });
  511. // Traverse and recreate the entire object list
  512. function traverseScene(object) {
  513. const objectData = getObjectData(object);
  514. if (objectData) {
  515. devTools.objects.set(object.uuid, objectData);
  516. dispatchEvent('observe', objectData);
  517. // Traverse children
  518. object.children.forEach(child => traverseScene(child));
  519. }
  520. }
  521. // Start traversal from scene root
  522. traverseScene(scene);
  523. } catch (error) {
  524. // If we get an "Extension context invalidated" error, stop monitoring
  525. if (error.message.includes('Extension context invalidated')) {
  526. clearInterval(intervalId);
  527. monitoringIntervals.delete(scene);
  528. devTools.reset();
  529. return;
  530. }
  531. console.warn('DevTools: Error in scene monitoring:', error);
  532. }
  533. }, 1000);
  534. monitoringIntervals.set(scene, intervalId);
  535. // Clean up monitoring when scene is disposed
  536. const originalDispose = scene.dispose;
  537. scene.dispose = function() {
  538. const intervalId = monitoringIntervals.get(this);
  539. if (intervalId) {
  540. clearInterval(intervalId);
  541. monitoringIntervals.delete(this);
  542. }
  543. if (originalDispose) {
  544. originalDispose.call(this);
  545. }
  546. };
  547. }
  548. // Function to manually reload scene objects
  549. function reloadSceneObjects(scene) {
  550. console.log('DevTools: Manually reloading scene objects for scene:', scene.uuid);
  551. // Track new objects to avoid duplicates
  552. const processedObjects = new Set();
  553. // Recursively observe all objects
  554. function observeObject(object) {
  555. if (!processedObjects.has(object.uuid)) {
  556. processedObjects.add(object.uuid);
  557. console.log('DevTools: Processing object during reload:', object.type || object.constructor.name, object.uuid);
  558. // Get object data
  559. const objectData = getObjectData(object);
  560. if (objectData) {
  561. if (devTools.objects.has(object.uuid)) {
  562. // Update existing object
  563. const existingData = devTools.objects.get(object.uuid);
  564. existingData.children = objectData.children;
  565. dispatchEvent('update', existingData);
  566. } else {
  567. // Add new object
  568. devTools.objects.set(object.uuid, objectData);
  569. dispatchEvent('observe', objectData);
  570. console.log('DevTools: New object observed during reload:', object.type || object.constructor.name);
  571. }
  572. }
  573. // Process children recursively
  574. if (object.children && object.children.length > 0) {
  575. console.log('DevTools: Processing', object.children.length, 'children of', object.type || object.constructor.name);
  576. object.children.forEach(child => observeObject(child));
  577. }
  578. }
  579. }
  580. // Start with the scene itself to ensure everything is traversed
  581. observeObject(scene);
  582. console.log('DevTools: Scene reload complete. Processed', processedObjects.size, 'objects');
  583. }
  584. } else {
  585. console.log('DevTools: Bridge already initialized');
  586. }
粤ICP备19079148号