Skip to content

Add export files feature to DevTools extension #31231

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

Open
wants to merge 3 commits into
base: devtools
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
187 changes: 164 additions & 23 deletions devtools/background.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,26 +3,41 @@
// Map tab IDs to connections
const connections = new Map();

// Handshake: Track tabs waiting for content script readiness
const pendingMessages = new Map();

// Listen for connections from the devtools panel
chrome.runtime.onConnect.addListener( port => {
chrome.runtime.onConnect.addListener( ( port ) => {

let tabId;

// Listen for messages from the devtools panel
port.onMessage.addListener( message => {
port.onMessage.addListener( ( message ) => {

//console.debug('Background: Received message from panel:', message);

if ( message.name === 'init' ) {

tabId = message.tabId;
connections.set( tabId, port );
console.debug( `Background: Connection initialized for tab ${tabId}` );

} else if ( message.name === 'request-state' && tabId ) {

chrome.tabs.sendMessage( tabId, message );

} else if ( message.name.startsWith( 'export-' ) && tabId ) {

console.log( 'Background: Received export-scene from panel:', message );
console.log( 'Background: Forwarding export-scene message to tab:', message );
chrome.tabs.sendMessage( tabId, message );

} else if ( tabId === undefined ) {

console.warn( 'Background: Message received from panel before init:', message );
console.warn(
'Background: Message received from panel before init:',
message
);

}

Expand All @@ -34,22 +49,101 @@ chrome.runtime.onConnect.addListener( port => {
if ( tabId ) {

connections.delete( tabId );
console.log( `Background: Connection closed for tab ${tabId}` );

}

} );

} );

// Enhanced error handling for port lifecycle
chrome.runtime.onConnect.addListener( ( port ) => {

let tabId;

port.onMessage.addListener( ( message ) => {

if ( message.name === 'init' ) {

tabId = message.tabId;
connections.set( tabId, port );

} else if ( tabId === undefined ) {

console.warn( 'Background: Message received from panel before init:', message );

}

} );

port.onDisconnect.addListener( () => {

if ( tabId ) {

connections.delete( tabId );
console.log( `Background: Connection closed for tab ${tabId}` );

}

} );

} );

// Listen for messages from the content script
// Ensure runtime context initialization
chrome.webNavigation.onCommitted.addListener( ( details ) => {

const { tabId, frameId } = details;

if ( frameId === 0 ) {

chrome.action.setBadgeText( { tabId: tabId, text: '' } ).catch( () => {
/* Tab might be gone */
} );

}

const port = connections.get( tabId );
if ( port ) {

port.postMessage( {
id: 'three-devtools',
name: 'committed',
frameId: frameId,
} );

}

} );


// Listen for handshake from content script
chrome.runtime.onMessage.addListener( ( message, sender, sendResponse ) => {

if ( message.name === 'three-devtools-content-ready' && sender.tab ) {

const tabId = sender.tab.id;
console.log( `[Three.js DevTools] Background received handshake from tab ${tabId}` );
if ( pendingMessages.has( tabId ) ) {

const { message: queuedMessage, retryCount } = pendingMessages.get( tabId );
console.log( `Background: Handshake received from tab ${tabId}, retrying message.` );
pendingMessages.delete( tabId );
// Retry sending the original message
sendMessageToTab( tabId, queuedMessage, retryCount );

}

}

//console.debug('Background: Received message from content script:', message);

if ( message.scheme ) {

chrome.action.setIcon( {
path: {
128: `icons/128-${message.scheme}.png`
}
128: `icons/128-${message.scheme}.png`,
},
} );

}
Expand Down Expand Up @@ -93,40 +187,87 @@ chrome.runtime.onMessage.addListener( ( message, sender, sendResponse ) => {

}

return false; // Return false to indicate synchronous handling
// --- BEGIN: Handle background download requests (for export fallback) ---
if ( message.type === 'request-background-download' && message.detail ) {

} );
const { filename, dataUrl, blob, binary } = message.detail;
let url = dataUrl;
let cleanupUrl = false;

// Listen for page navigation events
chrome.webNavigation.onCommitted.addListener( details => {
// If a Blob is provided, create a blob URL
if ( ! url && blob ) {

const { tabId, frameId } = details;
try {

// Clear badge on navigation, only for top-level navigation
if ( frameId === 0 ) {
const blobObj = new Blob( [ blob ], { type: binary ? 'model/gltf-binary' : 'application/octet-stream' } );
url = URL.createObjectURL( blobObj );
cleanupUrl = true;

chrome.action.setBadgeText( { tabId: tabId, text: '' } ).catch( () => { /* Tab might be gone */ } );
} catch ( e ) {

}
console.warn( 'Background: Failed to create blob URL for download:', e );
sendResponse && sendResponse( { error: e.message } );
return false;

const port = connections.get( tabId );
}

if ( port ) {
}

if ( ! url ) {

console.warn( 'Background: No dataUrl or blob provided for background download' );
sendResponse && sendResponse( { error: 'No dataUrl or blob provided' } );
return false;

}

chrome.downloads.download( {
url: url,
filename: filename || ( binary ? 'scene.glb' : 'scene.gltf' ),
saveAs: true
}, function ( downloadId ) {

if ( chrome.runtime.lastError ) {

console.warn( 'Background: Background download failed:', chrome.runtime.lastError );
sendResponse && sendResponse( { error: chrome.runtime.lastError.message } );

} else {

console.log( 'Background: Background download started with ID:', downloadId );
sendResponse && sendResponse( { success: true, downloadId: downloadId } );

}

// Clean up blob URL after a short delay
if ( cleanupUrl && url ) {

setTimeout( function () {

URL.revokeObjectURL( url );
console.log( 'Background: Cleaned up object URL for background download' );

}, 5000 );

}

port.postMessage( {
id: 'three-devtools',
name: 'committed',
frameId: frameId
} );

// Keep the message channel open for sendResponse
return true;

}
// --- END: Handle background download requests ---

} );
return false; // Return false to indicate synchronous handling

} );
// Clear badge when a tab is closed
chrome.tabs.onRemoved.addListener( ( tabId ) => {

chrome.action.setBadgeText( { tabId: tabId, text: '' } ).catch( () => { /* Tab might be gone */ } );
chrome.action.setBadgeText( { tabId: tabId, text: '' } ).catch( () => {
/* Tab might be gone */
} );

// Clean up connection if it exists for the closed tab
if ( connections.has( tabId ) ) {
Expand Down
Loading