DEV Community

Chris Atgmail
Chris Atgmail

Posted on

How to Intercept Blob URLs with Chrome DevTools Protocol

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);
};
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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);
        };
      `
    }
  }));
};
Enter fullscreen mode Exit fullscreen mode

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);
Enter fullscreen mode Exit fullscreen mode

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:

  1. No page modification visible — The original function still works, the page behaves normally
  2. Raw data capture — You get the actual decoded bytes, not a rendered screenshot
  3. Works on any site — As long as they use standard blob URLs
  4. 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)