content-script.js 3.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144
  1. /* global chrome */
  2. // This script runs in the context of the web page
  3. // Constants
  4. const MESSAGE_ID = 'three-devtools';
  5. const MESSAGE_REQUEST_STATE = 'request-state';
  6. const MESSAGE_REQUEST_OBJECT_DETAILS = 'request-object-details';
  7. const MESSAGE_SCROLL_TO_CANVAS = 'scroll-to-canvas';
  8. // Inject the bridge script into the main document or a target (e.g., iframe)
  9. function injectBridge( target = document ) {
  10. const script = document.createElement( 'script' );
  11. script.src = chrome.runtime.getURL( 'bridge.js' );
  12. script.onload = function () {
  13. this.remove();
  14. };
  15. ( target.head || target.documentElement ).appendChild( script );
  16. return script;
  17. }
  18. // Inject bridge into all existing iframes
  19. function injectIntoIframes() {
  20. document.querySelectorAll( 'iframe' ).forEach( iframe => {
  21. try {
  22. if ( iframe.contentDocument ) injectBridge( iframe.contentDocument );
  23. } catch ( e ) {
  24. // Ignore cross-origin errors when accessing iframe content
  25. }
  26. } );
  27. }
  28. // Initial injection
  29. injectBridge();
  30. injectIntoIframes();
  31. // Watch for new iframes being added
  32. new MutationObserver( mutations => {
  33. mutations.forEach( mutation => {
  34. mutation.addedNodes.forEach( node => {
  35. if ( node.tagName === 'IFRAME' ) {
  36. node.addEventListener( 'load', () => {
  37. try {
  38. if ( node.contentDocument ) injectBridge( node.contentDocument );
  39. } catch ( e ) {
  40. // Ignore cross-origin errors when accessing iframe content
  41. }
  42. } );
  43. }
  44. } );
  45. } );
  46. } ).observe( document.documentElement, { childList: true, subtree: true } );
  47. // Helper to check if extension context is valid
  48. function isExtensionContextValid() {
  49. try {
  50. chrome.runtime.getURL( '' );
  51. return true;
  52. } catch ( error ) {
  53. return false;
  54. }
  55. }
  56. // Unified message handler for window messages
  57. function handleWindowMessage( event ) {
  58. // Only accept messages with the correct id
  59. if ( ! event.data || event.data.id !== MESSAGE_ID ) return;
  60. // Determine source: 'main' for window, 'iframe' otherwise
  61. const source = event.source === window ? 'main' : 'iframe';
  62. if ( ! isExtensionContextValid() ) {
  63. console.warn( 'Extension context invalidated, cannot send message' );
  64. return;
  65. }
  66. event.data.source = source;
  67. chrome.runtime.sendMessage( event.data );
  68. }
  69. // Listener for messages from the background script (originating from panel)
  70. function handleBackgroundMessage( message ) {
  71. const forwardableMessages = new Set( [ MESSAGE_REQUEST_STATE, MESSAGE_REQUEST_OBJECT_DETAILS, MESSAGE_SCROLL_TO_CANVAS ] );
  72. if ( forwardableMessages.has( message.name ) ) {
  73. message.id = MESSAGE_ID;
  74. window.postMessage( message, '*' );
  75. }
  76. }
  77. // Add event listeners
  78. window.addEventListener( 'message', handleWindowMessage, false );
  79. chrome.runtime.onMessage.addListener( handleBackgroundMessage );
  80. // Icon color scheme
  81. const isLightTheme = window.matchMedia( '(prefers-color-scheme: light)' ).matches;
  82. chrome.runtime.sendMessage( { scheme: isLightTheme ? 'light' : 'dark' } );
  83. window.matchMedia( '(prefers-color-scheme: light)' ).onchange = event => {
  84. chrome.runtime.sendMessage( { scheme: event.matches ? 'light' : 'dark' } );
  85. };
粤ICP备19079148号