debugger Permission — Chrome Extension Reference

10 min read

debugger Permission — Chrome Extension Reference

Overview

manifest.json

{ "permissions": ["debugger"] }

Key APIs

chrome.debugger.attach(target, version, callback)

chrome.debugger.attach({ tabId: tab.id }, "1.3", () => {
  // Attached to tab, can now send CDP commands
  // Chrome shows "Extension is debugging this browser" infobar
});

chrome.debugger.sendCommand(target, method, params?, callback?)

// Get page DOM
chrome.debugger.sendCommand({ tabId }, "DOM.getDocument", {}, (result) => {
  console.log(result.root);
});
// Take screenshot
chrome.debugger.sendCommand({ tabId }, "Page.captureScreenshot", { format: "png" }, (result) => {
  const dataUrl = "data:image/png;base64," + result.data;
});
// Intercept network
chrome.debugger.sendCommand({ tabId }, "Network.enable", {});

chrome.debugger.detach(target, callback)

chrome.debugger.getTargets(callback)

chrome.debugger.onEvent

chrome.debugger.onEvent.addListener((source, method, params) => {
  if (method === "Network.responseReceived") {
    console.log("Response:", params.response.url, params.response.status);
  }
});

chrome.debugger.onDetach

Chrome DevTools Protocol (CDP) Domains

Common Patterns

Screenshot Capture

Network Monitor

Performance Profiling

Automated Testing

Security & UX Considerations

Using with @theluckystrike/webext-permissions

import { checkPermission, requestPermission, PERMISSION_DESCRIPTIONS } from "@theluckystrike/webext-permissions";

const result = await checkPermission("debugger");
console.log(result.description); // "Access the page debugger backend"
console.log(result.granted);

PERMISSION_DESCRIPTIONS.debugger; // "Access the page debugger backend"

// This is a very high-risk permission — consider optional_permissions
if (!result.granted) {
  const req = await requestPermission("debugger");
  if (!req.granted) return;
}

Using with @theluckystrike/webext-messaging

Pattern: popup triggers debug actions, background manages CDP sessions:

type Messages = {
  captureScreenshot: {
    request: { tabId: number; format?: "png" | "jpeg" };
    response: { dataUrl: string };
  };
  getPageMetrics: {
    request: { tabId: number };
    response: { domNodes: number; jsHeapSize: number; layoutCount: number };
  };
  detachDebugger: {
    request: { tabId: number };
    response: { detached: boolean };
  };
};

// background.ts
import { createMessenger } from "@theluckystrike/webext-messaging";
const msg = createMessenger<Messages>();

msg.onMessage({
  captureScreenshot: async ({ tabId, format }) => {
    await chrome.debugger.attach({ tabId }, "1.3");
    try {
      const result = await chrome.debugger.sendCommand(
        { tabId },
        "Page.captureScreenshot",
        { format: format || "png" }
      );
      return { dataUrl: `data:image/${format || "png"};base64,${(result as any).data}` };
    } finally {
      await chrome.debugger.detach({ tabId });
    }
  },
  getPageMetrics: async ({ tabId }) => {
    await chrome.debugger.attach({ tabId }, "1.3");
    try {
      await chrome.debugger.sendCommand({ tabId }, "Performance.enable", {});
      const metrics = await chrome.debugger.sendCommand({ tabId }, "Performance.getMetrics", {});
      const m = (metrics as any).metrics;
      return {
        domNodes: m.find((x: any) => x.name === "Nodes")?.value || 0,
        jsHeapSize: m.find((x: any) => x.name === "JSHeapUsedSize")?.value || 0,
        layoutCount: m.find((x: any) => x.name === "LayoutCount")?.value || 0,
      };
    } finally {
      await chrome.debugger.sendCommand({ tabId }, "Performance.disable", {});
      await chrome.debugger.detach({ tabId });
    }
  },
  detachDebugger: async ({ tabId }) => {
    await chrome.debugger.detach({ tabId });
    return { detached: true };
  },
});

Using with @theluckystrike/webext-storage

Store debug session preferences and captured data:

import { defineSchema, createStorage } from "@theluckystrike/webext-storage";

const schema = defineSchema({
  screenshotFormat: "png" as "png" | "jpeg",
  screenshotQuality: 80,
  capturedScreenshots: [] as Array<{ url: string; dataUrl: string; timestamp: number }>,
  networkLogEnabled: false,
});
const storage = createStorage({ schema });

// Watch for network logging toggle
storage.watch("networkLogEnabled", (enabled) => {
  if (enabled) console.log("Network logging activated");
  else console.log("Network logging deactivated");
});

Practical Example: Full-Page Screenshot

async function captureFullPage(tabId: number): Promise<string> {
  await chrome.debugger.attach({ tabId }, "1.3");

  try {
    // Get full page dimensions
    const layout = await chrome.debugger.sendCommand(
      { tabId },
      "Page.getLayoutMetrics",
      {}
    ) as any;

    const { width, height } = layout.contentSize;

    // Override viewport to full page size
    await chrome.debugger.sendCommand({ tabId }, "Emulation.setDeviceMetricsOverride", {
      width: Math.ceil(width),
      height: Math.ceil(height),
      deviceScaleFactor: 1,
      mobile: false,
    });

    // Capture
    const screenshot = await chrome.debugger.sendCommand(
      { tabId },
      "Page.captureScreenshot",
      { format: "png", fromSurface: true }
    ) as any;

    // Reset viewport
    await chrome.debugger.sendCommand({ tabId }, "Emulation.clearDeviceMetricsOverride", {});

    return `data:image/png;base64,${screenshot.data}`;
  } finally {
    await chrome.debugger.detach({ tabId });
  }
}

Gotchas

Common Errors

Frequently Asked Questions

What is the chrome.debugger API used for?

The chrome.debugger API allows extensions to instrument network traffic, debug JavaScript, and interact with pages using the Chrome DevTools Protocol.

Why does debugger require a permission warning?

The debugger API provides powerful capabilities that can intercept and modify page content, which is why it triggers a permission warning. —

Part of the Chrome Extension Guide by theluckystrike. Built at zovo.one.

No previous article
No next article