Ever wondered how to capture dynamically-created blob URLs before they're displayed in a webpage? I recently needed to do exactly this for a desktop app, and the Chrome DevTools Protocol (CDP) turned out to be the perfect solution.
The Problem
Many web apps download encrypted or protected content, decrypt it client-side using JavaScript or WebAssembly, then create a blob URL to display it. By the time the content appears on screen, it's already been processed and rendered.
If you want to capture that raw, decoded data programmatically—not a screenshot, but the actual bytes—you need to intercept it at the moment of blob creation.
The Solution: Override URL.createObjectURL
The key insight is that all blob URLs in the browser are created through URL.createObjectURL(). By injecting a script that wraps this function, we can intercept every blob before it gets a URL assigned.
Here's the core technique:
// Store the original function
const originalCreateObjectURL = URL.createObjectURL;
// Override with our interceptor
URL.createObjectURL = function(blob) {
// Check if it's an image blob worth capturing
if (blob instanceof Blob &&
blob.type.startsWith('image/') &&
blob.size > 30000) {
// Read the blob as base64
const reader = new FileReader();
reader.onloadend = () => {
// Store for later retrieval
window.__capturedBlob = {
data: reader.result.split(',')[1], // base64 portion
type: blob.type,
size: blob.size,
timestamp: Date.now()
};
};
reader.readAsDataURL(blob);
}
// Always call the original so the page works normally
return originalCreateObjectURL.call(this, blob);
};
This script captures image blobs larger than 30KB (to filter out tiny icons) without breaking the page's normal functionality.
Injecting via Chrome DevTools Protocol
To inject this into a running Chrome tab, you need Chrome launched with remote debugging enabled:
# macOS
/Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome --remote-debugging-port=9222
# Windows
chrome.exe --remote-debugging-port=9222
Then connect via CDP:
// 1. Get list of available tabs
const response = await fetch('http://localhost:9222/json');
const tabs = await response.json();
// 2. Find your target tab
const target = tabs.find(t => t.url.includes('your-target-site.com'));
// 3. Connect via WebSocket
const ws = new WebSocket(target.webSocketDebuggerUrl);
ws.onopen = () => {
// 4. Enable Runtime domain
ws.send(JSON.stringify({
id: 1,
method: 'Runtime.enable'
}));
// 5. Inject our interception script
ws.send(JSON.stringify({
id: 2,
method: 'Runtime.evaluate',
params: {
expression: `
const originalCreateObjectURL = URL.createObjectURL;
URL.createObjectURL = function(blob) {
if (blob instanceof Blob && blob.type.startsWith('image/') && blob.size > 30000) {
const reader = new FileReader();
reader.onloadend = () => {
window.__capturedBlob = {
data: reader.result.split(',')[1],
type: blob.type,
size: blob.size
};
};
reader.readAsDataURL(blob);
}
return originalCreateObjectURL.call(this, blob);
};
`
}
}));
};
Polling for Captured Data
Once injected, poll for captured blobs:
setInterval(() => {
ws.send(JSON.stringify({
id: Date.now(),
method: 'Runtime.evaluate',
params: {
expression: `
(() => {
if (window.__capturedBlob) {
const data = window.__capturedBlob;
window.__capturedBlob = null;
return JSON.stringify(data);
}
return null;
})()
`,
returnByValue: true
}
}));
}, 500);
When a blob is captured, you'll receive the base64-encoded data in the WebSocket response, which you can then decode and save.
Why This Works
This technique is powerful because:
- No page modification visible — The original function still works, the page behaves normally
- Raw data capture — You get the actual decoded bytes, not a rendered screenshot
- Works on any site — As long as they use standard blob URLs
- Invisible to page JavaScript — The page can't easily detect CDP connections
Real-World Application
I used this exact technique to build SnapNinja, a desktop app that captures media from Snapchat Web. The app connects to Chrome via CDP, injects the blob interceptor, and automatically saves images as users browse—capturing the original quality, decoded content.
Caveats
- Requires Chrome launched with
--remote-debugging-port - Only works for blob URLs (not canvas rendering or WebGL)
- Some sites may use other methods to display content
- The 30KB threshold might need adjustment for your use case
Wrapping Up
Chrome DevTools Protocol opens up a lot of possibilities for browser automation and content capture that aren't possible with regular extensions. Blob interception is just one technique—you can also intercept network requests, modify the DOM, capture screenshots, and much more.
If you're building developer tools, testing frameworks, or content capture utilities, CDP is worth exploring.
Have questions about CDP or blob interception? Drop a comment below!
Top comments (0)