<?xml version="1.0" encoding="utf-8"?><?xml-stylesheet type="text/xsl" href="rss.xsl"?>
<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/">
    <channel>
        <title>Let's Duduling Blog</title>
        <link>https://blog.duduling.dev/en/</link>
        <description>Let's Duduling Blog</description>
        <lastBuildDate>Sat, 18 Jul 2026 00:00:00 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <language>en</language>
        <item>
            <title><![CDATA[Hello Chrome Extension: My First Chrome Extension]]></title>
            <link>https://blog.duduling.dev/en/hello-chrome-extension</link>
            <guid>https://blog.duduling.dev/en/hello-chrome-extension</guid>
            <pubDate>Sat, 18 Jul 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Hello Chrome Extension: My First Chrome Extension - cover]]></description>
            <content:encoded><![CDATA[<p><img decoding="async" loading="lazy" alt="Hello Chrome Extension: My First Chrome Extension - cover" src="https://blog.duduling.dev/en/assets/images/cover-f65d55636bedc74cf89491cc1db4a56f.webp" width="1280" height="800" class="img_ev3q"></p>
<div class="theme-admonition theme-admonition-note admonition_xJq3 alert alert--secondary"><div class="admonitionHeading_Gvgb"><span class="admonitionIcon_Rf37"><svg viewBox="0 0 14 16"><path fill-rule="evenodd" d="M6.3 5.69a.942.942 0 0 1-.28-.7c0-.28.09-.52.28-.7.19-.18.42-.28.7-.28.28 0 .52.09.7.28.18.19.28.42.28.7 0 .28-.09.52-.28.7a1 1 0 0 1-.7.3c-.28 0-.52-.11-.7-.3zM8 7.99c-.02-.25-.11-.48-.31-.69-.2-.19-.42-.3-.69-.31H6c-.27.02-.48.13-.69.31-.2.2-.3.44-.31.69h1v3c.02.27.11.5.31.69.2.2.42.31.69.31h1c.27 0 .48-.11.69-.31.2-.19.3-.42.31-.69H8V7.98v.01zM7 2.3c-3.14 0-5.7 2.54-5.7 5.68 0 3.14 2.56 5.7 5.7 5.7s5.7-2.55 5.7-5.7c0-3.15-2.56-5.69-5.7-5.69v.01zM7 .98c3.86 0 7 3.14 7 7s-3.14 7-7 7-7-3.12-7-7 3.14-7 7-7z"></path></svg></span>note</div><div class="admonitionContent_BuS1"><p><strong>This project was built using Claude Code.🤖</strong></p><p>This article was translated from Korean to English using AI (and reviewed for technical accuracy).</p></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="why-i-built-this">Why I Built This<a href="https://blog.duduling.dev/en/hello-chrome-extension#why-i-built-this" class="hash-link" aria-label="Direct link to Why I Built This" title="Direct link to Why I Built This" translate="no">​</a></h2>
<p>Whether I was reporting a bug, sharing an API issue with a frontend/backend teammate, or these days asking an LLM to "take a look at why this request responds the way it does," I kept repeating the same tedious chore every time.</p>
<p>Open the Network panel in Chrome DevTools, click the request in question, find and copy the headers I need from the Headers tab, switch to the Payload tab to copy the request body, switch to the Response tab to copy the response body, and then reassemble all of it into readable Markdown. DevTools does offer features like "Copy as cURL" or "Copy Response," but they never give me the exact combination I actually want in one shot (e.g., status code + a few specific headers + body only).</p>
<p>What I really needed was "<strong>a tool that lets me pre-define the exact format I want, then copies a single request to the clipboard in exactly that format</strong>." That's how <strong>Picky Data</strong> came to be. The "Picky" in the name refers to picking out only what I want from a request/response, like a picky eater. In fact, during the early icon design process I leaned into this concept and settled on a fork-shaped icon — a metaphor for picking up data the same way a fork picks up food.</p>
<p>In this post, I'll walk through how this extension works and some of the interesting technical challenges I ran into while building it.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="what-i-built">What I Built<a href="https://blog.duduling.dev/en/hello-chrome-extension#what-i-built" class="hash-link" aria-label="Direct link to What I Built" title="Direct link to What I Built" translate="no">​</a></h2>
<p>Picky Data is an extension that works as a <strong>dedicated Chrome DevTools panel</strong>. Opening DevTools adds a new tab called "Picky Data," where:</p>
<ul>
<li class="">Network requests fired by the page are appended to the list in real time (the list clears automatically when you refresh the page)</li>
<li class="">You can narrow the list by method/type (fetch, doc, css, js, image, font, media, websocket, wasm, etc.) and by URL search text</li>
<li class="">Clicking a request shows a <strong>preview rendered according to my configured template</strong> on the right</li>
<li class="">Pressing the shortcut (default <code>Cmd/Ctrl+Shift+C</code>) copies that content straight to the clipboard</li>
<li class="">The copy template, which request/response headers to include, and the shortcut can all be freely changed from the options page</li>
</ul>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">manifest.json      Chrome extension manifest (MV3)</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">devtools.html/js   Registers the DevTools panel</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">panel.html/css/js  DevTools panel UI and logic</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">options.html/js    Options page UI and logic</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">theme.css          Shared light/dark theme variables</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">lib/defaults.js    Default template, shortcut, storage keys</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">lib/formatter.js   Template token substitution and header filter helpers</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">icons/             Extension icons</span><br></div></code></pre></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="design-principle-no-build-tools-minimal-permissions">Design Principle: No Build Tools, Minimal Permissions<a href="https://blog.duduling.dev/en/hello-chrome-extension#design-principle-no-build-tools-minimal-permissions" class="hash-link" aria-label="Direct link to Design Principle: No Build Tools, Minimal Permissions" title="Direct link to Design Principle: No Build Tools, Minimal Permissions" translate="no">​</a></h2>
<p>There was no real time pressure on the overall development, but I deliberately decided <strong>not to use any build tools</strong> this time. No bundler, no transpiler, no framework. <code>panel.html</code> simply loads <code>&lt;script src="lib/defaults.js"&gt;</code>, <code>&lt;script src="lib/formatter.js"&gt;</code>, and <code>&lt;script src="panel.js"&gt;</code> in order, and each script exposes its values as globals (<code>PICKY_DATA_DEFAULTS</code>, <code>PickyDataFormatter</code>) without any module system. Clicking "Load unpacked" in <code>chrome://extensions</code> just works — with no build step, the deployment pipeline is essentially just zipping the files.</p>
<p>That said, I didn't give up on type safety. I turned on <code>checkJs: true</code> in <code>jsconfig.json</code> and annotated every function with JSDoc types, so the TypeScript compiler could check the plain JS files directly. <code>devDependencies</code> has exactly two entries, <code>typescript</code> and <code>@types/chrome</code>, used solely for <code>npx tsc --noEmit</code> (and editor IntelliSense). For example, the shortcut type in <code>lib/defaults.js</code> is declared like this:</p>
<div class="language-js codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-js codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token doc-comment comment" style="color:#999988;font-style:italic">/**</span><br></div><div class="token-line" style="color:#393A34"><span class="token doc-comment comment" style="color:#999988;font-style:italic"> * </span><span class="token doc-comment comment keyword" style="color:#00009f;font-style:italic">@typedef</span><span class="token doc-comment comment" style="color:#999988;font-style:italic"> </span><span class="token doc-comment comment class-name punctuation" style="color:#393A34;font-style:italic">{</span><span class="token doc-comment comment class-name" style="color:#999988;font-style:italic">Object</span><span class="token doc-comment comment class-name punctuation" style="color:#393A34;font-style:italic">}</span><span class="token doc-comment comment" style="color:#999988;font-style:italic"> </span><span class="token doc-comment comment class-name" style="color:#999988;font-style:italic">Shortcut</span><span class="token doc-comment comment" style="color:#999988;font-style:italic"></span><br></div><div class="token-line" style="color:#393A34"><span class="token doc-comment comment" style="color:#999988;font-style:italic"> * </span><span class="token doc-comment comment keyword" style="color:#00009f;font-style:italic">@property</span><span class="token doc-comment comment" style="color:#999988;font-style:italic"> </span><span class="token doc-comment comment class-name punctuation" style="color:#393A34;font-style:italic">{</span><span class="token doc-comment comment class-name" style="color:#999988;font-style:italic">string</span><span class="token doc-comment comment class-name punctuation" style="color:#393A34;font-style:italic">}</span><span class="token doc-comment comment" style="color:#999988;font-style:italic"> </span><span class="token doc-comment comment parameter" style="color:#999988;font-style:italic">key</span><span class="token doc-comment comment" style="color:#999988;font-style:italic"> - The key value, stored uppercase (e.g. "C").</span><br></div><div class="token-line" style="color:#393A34"><span class="token doc-comment comment" style="color:#999988;font-style:italic"> * </span><span class="token doc-comment comment keyword" style="color:#00009f;font-style:italic">@property</span><span class="token doc-comment comment" style="color:#999988;font-style:italic"> </span><span class="token doc-comment comment class-name punctuation" style="color:#393A34;font-style:italic">{</span><span class="token doc-comment comment class-name" style="color:#999988;font-style:italic">boolean</span><span class="token doc-comment comment class-name punctuation" style="color:#393A34;font-style:italic">}</span><span class="token doc-comment comment" style="color:#999988;font-style:italic"> </span><span class="token doc-comment comment parameter" style="color:#999988;font-style:italic">ctrlOrCmd</span><span class="token doc-comment comment" style="color:#999988;font-style:italic"> - metaKey (Cmd) on Mac, ctrlKey on Windows/Linux.</span><br></div><div class="token-line" style="color:#393A34"><span class="token doc-comment comment" style="color:#999988;font-style:italic"> * </span><span class="token doc-comment comment keyword" style="color:#00009f;font-style:italic">@property</span><span class="token doc-comment comment" style="color:#999988;font-style:italic"> </span><span class="token doc-comment comment class-name punctuation" style="color:#393A34;font-style:italic">{</span><span class="token doc-comment comment class-name" style="color:#999988;font-style:italic">boolean</span><span class="token doc-comment comment class-name punctuation" style="color:#393A34;font-style:italic">}</span><span class="token doc-comment comment" style="color:#999988;font-style:italic"> </span><span class="token doc-comment comment parameter" style="color:#999988;font-style:italic">shift</span><span class="token doc-comment comment" style="color:#999988;font-style:italic"></span><br></div><div class="token-line" style="color:#393A34"><span class="token doc-comment comment" style="color:#999988;font-style:italic"> * </span><span class="token doc-comment comment keyword" style="color:#00009f;font-style:italic">@property</span><span class="token doc-comment comment" style="color:#999988;font-style:italic"> </span><span class="token doc-comment comment class-name punctuation" style="color:#393A34;font-style:italic">{</span><span class="token doc-comment comment class-name" style="color:#999988;font-style:italic">boolean</span><span class="token doc-comment comment class-name punctuation" style="color:#393A34;font-style:italic">}</span><span class="token doc-comment comment" style="color:#999988;font-style:italic"> </span><span class="token doc-comment comment parameter" style="color:#999988;font-style:italic">alt</span><span class="token doc-comment comment" style="color:#999988;font-style:italic"></span><br></div><div class="token-line" style="color:#393A34"><span class="token doc-comment comment" style="color:#999988;font-style:italic"> */</span><br></div></code></pre></div></div>
<p><code>manifest.json</code> also declares only the bare minimum.</p>
<div class="language-json codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-json codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token property" style="color:#36acaa">"manifest_version"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">3</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token property" style="color:#36acaa">"devtools_page"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"devtools.html"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token property" style="color:#36acaa">"options_page"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"options.html"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token property" style="color:#36acaa">"permissions"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token string" style="color:#e3116c">"storage"</span><span class="token punctuation" style="color:#393A34">]</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></div></code></pre></div></div>
<p>There's no host permission, no background/service worker, and no content script. The only permission requested is <code>storage</code>, and all settings are stored solely in <code>chrome.storage.local</code>. The network request data itself is only ever handled within the context DevTools already opens up, and it's never sent anywhere else — a privacy-friendly choice that helped on both the store review and user trust fronts.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="a-closer-look-at-the-implementation">A Closer Look at the Implementation<a href="https://blog.duduling.dev/en/hello-chrome-extension#a-closer-look-at-the-implementation" class="hash-link" aria-label="Direct link to A Closer Look at the Implementation" title="Direct link to A Closer Look at the Implementation" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="1-token-based-template-engine">1. Token-Based Template Engine<a href="https://blog.duduling.dev/en/hello-chrome-extension#1-token-based-template-engine" class="hash-link" aria-label="Direct link to 1. Token-Based Template Engine" title="Direct link to 1. Token-Based Template Engine" translate="no">​</a></h3>
<p>The core logic all lives in a single <code>render()</code> function in <code>lib/formatter.js</code>. When a user defines a template like this on the options page:</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain"># {{method}} {{url}}</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">Status: {{status}} {{statusText}}</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">## Request Headers</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">{{requestHeaders}}</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">## Request Body</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">{{requestBody}}</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">## Response Headers</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">{{responseHeaders}}</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">## Response Body</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">{{responseBody}}</span><br></div></code></pre></div></div>
<p><code>render()</code> builds 11 token values — <code>method</code>, <code>url</code>, <code>host</code>, <code>path</code>, <code>query</code>, <code>status</code>, <code>statusText</code>, <code>requestHeaders</code>, <code>responseHeaders</code>, <code>requestBody</code>, <code>responseBody</code> — from the captured HAR entry, and substitutes them via a regular expression.</p>
<div class="language-js codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-js codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token keyword control-flow" style="color:#00009f">return</span><span class="token plain"> template</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">replace</span><span class="token punctuation" style="color:#393A34">(</span><span class="token regex regex-delimiter" style="color:#36acaa">/</span><span class="token regex regex-source language-regex" style="color:#36acaa">{{</span><span class="token regex regex-source language-regex char-set class-name" style="color:#36acaa">\s</span><span class="token regex regex-source language-regex quantifier number" style="color:#36acaa">*</span><span class="token regex regex-source language-regex group punctuation" style="color:#393A34">(</span><span class="token regex regex-source language-regex char-class char-class-punctuation punctuation" style="color:#393A34">[</span><span class="token regex regex-source language-regex char-class range" style="color:#36acaa">a</span><span class="token regex regex-source language-regex char-class range range-punctuation operator" style="color:#393A34">-</span><span class="token regex regex-source language-regex char-class range" style="color:#36acaa">z</span><span class="token regex regex-source language-regex char-class range" style="color:#36acaa">A</span><span class="token regex regex-source language-regex char-class range range-punctuation operator" style="color:#393A34">-</span><span class="token regex regex-source language-regex char-class range" style="color:#36acaa">Z</span><span class="token regex regex-source language-regex char-class char-class-punctuation punctuation" style="color:#393A34">]</span><span class="token regex regex-source language-regex quantifier number" style="color:#36acaa">+</span><span class="token regex regex-source language-regex group punctuation" style="color:#393A34">)</span><span class="token regex regex-source language-regex char-set class-name" style="color:#36acaa">\s</span><span class="token regex regex-source language-regex quantifier number" style="color:#36acaa">*</span><span class="token regex regex-source language-regex" style="color:#36acaa">}}</span><span class="token regex regex-delimiter" style="color:#36acaa">/</span><span class="token regex regex-flags" style="color:#36acaa">g</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token parameter">match</span><span class="token parameter punctuation" style="color:#393A34">,</span><span class="token parameter"> key</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token arrow operator" style="color:#393A34">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword control-flow" style="color:#00009f">return</span><span class="token plain"> </span><span class="token class-name">Object</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">prototype</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">hasOwnProperty</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">call</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">tokens</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> key</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token operator" style="color:#393A34">?</span><span class="token plain"> tokens</span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain">key</span><span class="token punctuation" style="color:#393A34">]</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> match</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><br></div></code></pre></div></div>
<p>The <code>hasOwnProperty</code> guard is the noteworthy part here. When it encounters an unsupported token (say, a typo like <code>{{metod}}</code>), instead of wiping it out to an empty string, it <strong>leaves the original string untouched</strong>. It's a small but deliberate choice that lets users immediately notice a mistake in their own template.</p>
<p><code>host</code>/<code>path</code>/<code>query</code> aren't parsed manually from the string — that's delegated to the <code>URL</code> constructor.</p>
<div class="language-js codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-js codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">function</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">getUrlParts</span><span class="token punctuation" style="color:#393A34">(</span><span class="token parameter">urlString</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword control-flow" style="color:#00009f">try</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> u </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">new</span><span class="token plain"> </span><span class="token class-name">URL</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">urlString</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword control-flow" style="color:#00009f">return</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token literal-property property" style="color:#36acaa">host</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> u</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">host</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token literal-property property" style="color:#36acaa">path</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> u</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">pathname</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token literal-property property" style="color:#36acaa">query</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> u</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">search</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">?</span><span class="token plain"> u</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">search</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">slice</span><span class="token punctuation" style="color:#393A34">(</span><span class="token number" style="color:#36acaa">1</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">''</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token keyword control-flow" style="color:#00009f">catch</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">e</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword control-flow" style="color:#00009f">return</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> </span><span class="token literal-property property" style="color:#36acaa">host</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">''</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token literal-property property" style="color:#36acaa">path</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">''</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token literal-property property" style="color:#36acaa">query</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">''</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></div></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="2-pretty-print-json-bodies-leave-everything-else-as-is">2. Pretty-Print JSON Bodies, Leave Everything Else As-Is<a href="https://blog.duduling.dev/en/hello-chrome-extension#2-pretty-print-json-bodies-leave-everything-else-as-is" class="hash-link" aria-label="Direct link to 2. Pretty-Print JSON Bodies, Leave Everything Else As-Is" title="Direct link to 2. Pretty-Print JSON Bodies, Leave Everything Else As-Is" translate="no">​</a></h3>
<p>Request/response bodies are sometimes JSON and sometimes not. <code>prettifyBody()</code> indents them via <code>JSON.parse</code> followed by <code>JSON.stringify(…, null, 2)</code>, but if parsing fails, it silently returns the original text unchanged.</p>
<div class="language-js codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-js codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">function</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">prettifyBody</span><span class="token punctuation" style="color:#393A34">(</span><span class="token parameter">text</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword control-flow" style="color:#00009f">if</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token operator" style="color:#393A34">!</span><span class="token plain">text</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token keyword control-flow" style="color:#00009f">return</span><span class="token plain"> text</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword control-flow" style="color:#00009f">try</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword control-flow" style="color:#00009f">return</span><span class="token plain"> </span><span class="token known-class-name class-name">JSON</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">stringify</span><span class="token punctuation" style="color:#393A34">(</span><span class="token known-class-name class-name">JSON</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">parse</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">text</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token keyword null nil" style="color:#00009f">null</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">2</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token keyword control-flow" style="color:#00009f">catch</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">e</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword control-flow" style="color:#00009f">return</span><span class="token plain"> text</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></div></code></pre></div></div>
<p>It's a simple rule — "pretty-print if it's JSON, leave it alone otherwise" — but in practice it doesn't break even when HTML or plain-text responses show up mixed in.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="3-header-filters--a-bridge-between-three-representations">3. Header Filters — A Bridge Between Three Representations<a href="https://blog.duduling.dev/en/hello-chrome-extension#3-header-filters--a-bridge-between-three-representations" class="hash-link" aria-label="Direct link to 3. Header Filters — A Bridge Between Three Representations" title="Direct link to 3. Header Filters — A Bridge Between Three Representations" translate="no">​</a></h3>
<p>On the options page, header filters are managed as a checkbox list (<code>[{ name, enabled }, ...]</code>), but when saved and passed around they become a comma-separated string, and at actual matching time they become a lowercase array. The functions bridging these three representations are <code>filtersToString()</code> and <code>parseHeaderFilter()</code>.</p>
<div class="language-js codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-js codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">function</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">filtersToString</span><span class="token punctuation" style="color:#393A34">(</span><span class="token parameter">filters</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword control-flow" style="color:#00009f">if</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token operator" style="color:#393A34">!</span><span class="token plain">filters </span><span class="token operator" style="color:#393A34">||</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">!</span><span class="token plain">filters</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">length</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token keyword control-flow" style="color:#00009f">return</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">''</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword control-flow" style="color:#00009f">return</span><span class="token plain"> filters</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">filter</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">(</span><span class="token parameter">f</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token arrow operator" style="color:#393A34">=&gt;</span><span class="token plain"> f </span><span class="token operator" style="color:#393A34">&amp;&amp;</span><span class="token plain"> f</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">enabled</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">&amp;&amp;</span><span class="token plain"> f</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">name</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">&amp;&amp;</span><span class="token plain"> f</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">name</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">trim</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">map</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">(</span><span class="token parameter">f</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token arrow operator" style="color:#393A34">=&gt;</span><span class="token plain"> f</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">name</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">trim</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">join</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">','</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">function</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">parseHeaderFilter</span><span class="token punctuation" style="color:#393A34">(</span><span class="token parameter">str</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword control-flow" style="color:#00009f">if</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token operator" style="color:#393A34">!</span><span class="token plain">str</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token keyword control-flow" style="color:#00009f">return</span><span class="token plain"> </span><span class="token keyword null nil" style="color:#00009f">null</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> names </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token known-class-name class-name">String</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">str</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">split</span><span class="token punctuation" style="color:#393A34">(</span><span class="token regex regex-delimiter" style="color:#36acaa">/</span><span class="token regex regex-source language-regex char-class char-class-punctuation punctuation" style="color:#393A34">[</span><span class="token regex regex-source language-regex char-class" style="color:#36acaa">,</span><span class="token regex regex-source language-regex char-class escape" style="color:#36acaa">\n</span><span class="token regex regex-source language-regex char-class char-class-punctuation punctuation" style="color:#393A34">]</span><span class="token regex regex-delimiter" style="color:#36acaa">/</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">map</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">(</span><span class="token parameter">s</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token arrow operator" style="color:#393A34">=&gt;</span><span class="token plain"> s</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">trim</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">toLowerCase</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">filter</span><span class="token punctuation" style="color:#393A34">(</span><span class="token known-class-name class-name">Boolean</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword control-flow" style="color:#00009f">return</span><span class="token plain"> names</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">length</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">?</span><span class="token plain"> names </span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token keyword null nil" style="color:#00009f">null</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></div></code></pre></div></div>
<p>There's an actual bug fix behind this. Initially, filtered headers were printed in their <strong>original wire order</strong>, but if a user wrote their filter list as <code>content-type, authorization</code>, it's more intuitive for the output to follow that same order. So <code>headersToString()</code> was flipped to <strong>iterate over the filter array</strong> and look up matching headers, instead of iterating over the header array.</p>
<div class="language-js codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-js codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">function</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">headersToString</span><span class="token punctuation" style="color:#393A34">(</span><span class="token parameter">headers</span><span class="token parameter punctuation" style="color:#393A34">,</span><span class="token parameter"> filter</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword control-flow" style="color:#00009f">if</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token operator" style="color:#393A34">!</span><span class="token plain">headers </span><span class="token operator" style="color:#393A34">||</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">!</span><span class="token plain">headers</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">length</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token keyword control-flow" style="color:#00009f">return</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">''</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword control-flow" style="color:#00009f">if</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token operator" style="color:#393A34">!</span><span class="token plain">filter</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token keyword control-flow" style="color:#00009f">return</span><span class="token plain"> headers</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">map</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">(</span><span class="token parameter">h</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token arrow operator" style="color:#393A34">=&gt;</span><span class="token plain"> </span><span class="token template-string template-punctuation string" style="color:#e3116c">`</span><span class="token template-string string" style="color:#e3116c">- </span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:#393A34">${</span><span class="token template-string interpolation">h</span><span class="token template-string interpolation punctuation" style="color:#393A34">.</span><span class="token template-string interpolation property-access">name</span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:#393A34">}</span><span class="token template-string string" style="color:#e3116c">: </span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:#393A34">${</span><span class="token template-string interpolation">h</span><span class="token template-string interpolation punctuation" style="color:#393A34">.</span><span class="token template-string interpolation property-access">value</span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:#393A34">}</span><span class="token template-string template-punctuation string" style="color:#e3116c">`</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">join</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'\n'</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token comment" style="color:#999988;font-style:italic">// Output follows the order written in the filter list, not the original header order.</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> lines </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  filter</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">forEach</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">(</span><span class="token parameter">name</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token arrow operator" style="color:#393A34">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    headers</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">filter</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">(</span><span class="token parameter">h</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token arrow operator" style="color:#393A34">=&gt;</span><span class="token plain"> </span><span class="token known-class-name class-name">String</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">h</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">name</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">toLowerCase</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">===</span><span class="token plain"> name</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">forEach</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">(</span><span class="token parameter">h</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token arrow operator" style="color:#393A34">=&gt;</span><span class="token plain"> lines</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">push</span><span class="token punctuation" style="color:#393A34">(</span><span class="token template-string template-punctuation string" style="color:#e3116c">`</span><span class="token template-string string" style="color:#e3116c">- </span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:#393A34">${</span><span class="token template-string interpolation">h</span><span class="token template-string interpolation punctuation" style="color:#393A34">.</span><span class="token template-string interpolation property-access">name</span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:#393A34">}</span><span class="token template-string string" style="color:#e3116c">: </span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:#393A34">${</span><span class="token template-string interpolation">h</span><span class="token template-string interpolation punctuation" style="color:#393A34">.</span><span class="token template-string interpolation property-access">value</span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:#393A34">}</span><span class="token template-string template-punctuation string" style="color:#e3116c">`</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword control-flow" style="color:#00009f">return</span><span class="token plain"> lines</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">join</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'\n'</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></div></code></pre></div></div>
<p>When the filter is empty (<code>null</code>), all headers are included in their original order — a safe default of "show everything if no filter was set."</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="4-devtools-doesnt-follow-the-os-dark-mode">4. DevTools Doesn't Follow the OS Dark Mode<a href="https://blog.duduling.dev/en/hello-chrome-extension#4-devtools-doesnt-follow-the-os-dark-mode" class="hash-link" aria-label="Direct link to 4. DevTools Doesn't Follow the OS Dark Mode" title="Direct link to 4. DevTools Doesn't Follow the OS Dark Mode" translate="no">​</a></h3>
<p>Chrome DevTools has its <strong>own Appearance setting</strong> (Light/Dark/System) that is completely independent of the OS or browser's <code>prefers-color-scheme</code>. In other words, it's common for a user to have the OS in light mode while DevTools itself is set to dark. If you build relying solely on <code>@media (prefers-color-scheme: dark)</code>, this kind of user ends up with a panel that's stuck showing light mode — a bug.</p>
<p>In the DevTools panel (<code>panel.js</code>), I read <code>chrome.devtools.panels.themeName</code>, which the extension has access to, directly and apply it.</p>
<div class="language-js codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-js codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token comment" style="color:#999988;font-style:italic">// DevTools' own theme (Appearance setting) is independent of the OS/browser prefers-color-scheme</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">// and can be chosen directly by the user, so we read chrome.devtools.panels.themeName and apply it.</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">function</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">applyDevtoolsTheme</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token dom variable" style="color:#36acaa">document</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">documentElement</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">dataset</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">theme</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    chrome</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">devtools</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">panels</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">themeName</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">===</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'dark'</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">?</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'dark'</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'light'</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></div></code></pre></div></div>
<p>The problem is that <strong>the options page has no access to the DevTools API</strong> (the options page is a regular extension page, not a DevTools panel). So <code>theme.css</code> layers two different resolution strategies on top of a single set of variables.</p>
<div class="language-css codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-css codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token selector pseudo-class" style="color:#00009f">:root</span><span class="token selector attribute punctuation" style="color:#393A34">[</span><span class="token selector attribute attr-name" style="color:#00a4db">data-theme</span><span class="token selector attribute operator" style="color:#393A34">=</span><span class="token selector attribute attr-value" style="color:#e3116c">'dark'</span><span class="token selector attribute punctuation" style="color:#393A34">]</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token variable" style="color:#36acaa">--bg</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token hexcode color">#1e1e1e</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token variable" style="color:#36acaa">--fg</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token hexcode color">#e8e8e8</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"> </span><span class="token comment" style="color:#999988;font-style:italic">/* … */</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">/* Fallback for pages like options.html that can't set data-theme */</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token atrule rule" style="color:#00a4db">@media</span><span class="token atrule" style="color:#00a4db"> </span><span class="token atrule punctuation" style="color:#393A34">(</span><span class="token atrule property" style="color:#36acaa">prefers-color-scheme</span><span class="token atrule punctuation" style="color:#393A34">:</span><span class="token atrule" style="color:#00a4db"> dark</span><span class="token atrule punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token selector pseudo-class" style="color:#00009f">:root</span><span class="token selector pseudo-class" style="color:#00009f">:not</span><span class="token selector punctuation" style="color:#393A34">(</span><span class="token selector attribute punctuation" style="color:#393A34">[</span><span class="token selector attribute attr-name" style="color:#00a4db">data-theme</span><span class="token selector attribute punctuation" style="color:#393A34">]</span><span class="token selector punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token variable" style="color:#36acaa">--bg</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token hexcode color">#1e1e1e</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token variable" style="color:#36acaa">--fg</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token hexcode color">#e8e8e8</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"> </span><span class="token comment" style="color:#999988;font-style:italic">/* … */</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></div></code></pre></div></div>
<p>On the panel page, JS reliably sets the <code>data-theme</code> attribute, so the <code>:root[data-theme="dark"]</code> rule wins. The options page never has <code>data-theme</code> at all, so the <code>:not([data-theme])</code> fallback follows the OS setting instead. It's the same set of color variables, filled in by a different signal depending on context. This part didn't work perfectly the first time either — it took a few rounds of fixes before the DevTools panel reliably followed the actual DevTools theme.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="5-lazy-loaded-cached-previews--and-guarding-against-stale-responses">5. Lazy-Loaded, Cached Previews — and Guarding Against Stale Responses<a href="https://blog.duduling.dev/en/hello-chrome-extension#5-lazy-loaded-cached-previews--and-guarding-against-stale-responses" class="hash-link" aria-label="Direct link to 5. Lazy-Loaded, Cached Previews — and Guarding Against Stale Responses" title="Direct link to 5. Lazy-Loaded, Cached Previews — and Guarding Against Stale Responses" translate="no">​</a></h3>
<p>Response bodies can only be fetched <strong>asynchronously</strong>, via DevTools' <code>entry.getContent(callback)</code>. Re-fetching it every time a request is selected, or every time the Copy/Full preview is toggled, would be wasteful, so content that's already been fetched once is cached in <code>previewContentId</code>/<code>previewContent</code>.</p>
<div class="language-js codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-js codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token keyword control-flow" style="color:#00009f">if</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">previewContentId </span><span class="token operator" style="color:#393A34">===</span><span class="token plain"> id</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  pre</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">textContent</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token maybe-class-name">PickyDataFormatter</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">render</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    currentTemplate</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    entry</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    previewContent </span><span class="token operator" style="color:#393A34">||</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">''</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token function" style="color:#d73a49">renderOptionsForMode</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">previewMode</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword control-flow" style="color:#00009f">return</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">entry</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">getContent</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">(</span><span class="token parameter">content</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token arrow operator" style="color:#393A34">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword control-flow" style="color:#00009f">if</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">selectedId </span><span class="token operator" style="color:#393A34">!==</span><span class="token plain"> id</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token keyword control-flow" style="color:#00009f">return</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"> </span><span class="token comment" style="color:#999988;font-style:italic">// Ignore if a different request was selected while loading</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  previewContentId </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> id</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  previewContent </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> content </span><span class="token operator" style="color:#393A34">||</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">''</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  pre</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">textContent</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token maybe-class-name">PickyDataFormatter</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">render</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    currentTemplate</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    entry</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    previewContent</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token function" style="color:#d73a49">renderOptionsForMode</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">previewMode</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><br></div></code></pre></div></div>
<p>The single line <code>if (selectedId !== id) return;</code> guards against a common async race condition. If a user clicks request A and, while waiting for the <code>getContent()</code> callback, clicks request B, then by the time A's callback finally arrives the selection has already changed to B. Without this guard, you'd end up with a bug where you're looking at B but the preview gets overwritten with A's content.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="6-copying-to-clipboard-execcommand-first-clipboard-api-as-fallback">6. Copying to Clipboard: execCommand First, Clipboard API as Fallback<a href="https://blog.duduling.dev/en/hello-chrome-extension#6-copying-to-clipboard-execcommand-first-clipboard-api-as-fallback" class="hash-link" aria-label="Direct link to 6. Copying to Clipboard: execCommand First, Clipboard API as Fallback" title="Direct link to 6. Copying to Clipboard: execCommand First, Clipboard API as Fallback" translate="no">​</a></h3>
<div class="language-js codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-js codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">function</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">copyToClipboard</span><span class="token punctuation" style="color:#393A34">(</span><span class="token parameter">text</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword" style="color:#00009f">let</span><span class="token plain"> copied </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token boolean" style="color:#36acaa">false</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword control-flow" style="color:#00009f">try</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    copyBuffer</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">value</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> text</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    copyBuffer</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">style</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">display</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'block'</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    copyBuffer</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">focus</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    copyBuffer</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">select</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    copied </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token dom variable" style="color:#36acaa">document</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">execCommand</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'copy'</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    copyBuffer</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">style</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">display</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'none'</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token keyword control-flow" style="color:#00009f">catch</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">e</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    copied </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token boolean" style="color:#36acaa">false</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword control-flow" style="color:#00009f">if</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">copied</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token function" style="color:#d73a49">showToast</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'Copied to clipboard.'</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword control-flow" style="color:#00009f">return</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword control-flow" style="color:#00009f">if</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token dom variable" style="color:#36acaa">navigator</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">clipboard</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">&amp;&amp;</span><span class="token plain"> </span><span class="token dom variable" style="color:#36acaa">navigator</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">clipboard</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">writeText</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token dom variable" style="color:#36acaa">navigator</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">clipboard</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">writeText</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">text</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">then</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token arrow operator" style="color:#393A34">=&gt;</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">showToast</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'Copied to clipboard.'</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token punctuation" style="color:#393A34">.</span><span class="token keyword control-flow" style="color:#00009f">catch</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token arrow operator" style="color:#393A34">=&gt;</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">showToast</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'Copy failed.'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token boolean" style="color:#36acaa">true</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token keyword control-flow" style="color:#00009f">else</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token function" style="color:#d73a49">showToast</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'Copy failed.'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token boolean" style="color:#36acaa">true</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></div></code></pre></div></div>
<p>It's tempting to reach for the modern <code>navigator.clipboard.writeText</code> first, but in the special context of a DevTools panel (user-gesture handling, focus state, etc.), the older approach of setting a value on a hidden <code>&lt;textarea&gt;</code> and calling <code>execCommand('copy')</code> turned out to work more reliably, so I kept it as the primary method and placed the Clipboard API as the fallback for when it fails.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="7-saving-options--instant-reflection-in-the-panel">7. Saving Options → Instant Reflection in the Panel<a href="https://blog.duduling.dev/en/hello-chrome-extension#7-saving-options--instant-reflection-in-the-panel" class="hash-link" aria-label="Direct link to 7. Saving Options → Instant Reflection in the Panel" title="Direct link to 7. Saving Options → Instant Reflection in the Panel" translate="no">​</a></h3>
<p>When a template or header filter is saved on the options page, it's reflected immediately without having to refresh the panel. This is all handled by a single <code>chrome.storage.onChanged</code> listener.</p>
<div class="language-js codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-js codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">chrome</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">storage</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">onChanged</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">addListener</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">(</span><span class="token parameter">changes</span><span class="token parameter punctuation" style="color:#393A34">,</span><span class="token parameter"> area</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token arrow operator" style="color:#393A34">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword control-flow" style="color:#00009f">if</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">area </span><span class="token operator" style="color:#393A34">!==</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'local'</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token keyword control-flow" style="color:#00009f">return</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword" style="color:#00009f">let</span><span class="token plain"> shouldRerenderPreview </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token boolean" style="color:#36acaa">false</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword control-flow" style="color:#00009f">if</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">changes</span><span class="token punctuation" style="color:#393A34">[</span><span class="token constant" style="color:#36acaa">PICKY_DATA_DEFAULTS</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">storageKeys</span><span class="token punctuation" style="color:#393A34">.</span><span class="token constant" style="color:#36acaa">TEMPLATE</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    currentTemplate </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> changes</span><span class="token punctuation" style="color:#393A34">[</span><span class="token spread operator" style="color:#393A34">...</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">newValue</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">||</span><span class="token plain"> </span><span class="token constant" style="color:#36acaa">PICKY_DATA_DEFAULTS</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">template</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    shouldRerenderPreview </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token boolean" style="color:#36acaa">true</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token comment" style="color:#999988;font-style:italic">// ... SHORTCUT, REQUEST_HEADER_FILTER, RESPONSE_HEADER_FILTER follow the same pattern</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword control-flow" style="color:#00009f">if</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">shouldRerenderPreview </span><span class="token operator" style="color:#393A34">&amp;&amp;</span><span class="token plain"> selectedId </span><span class="token operator" style="color:#393A34">!==</span><span class="token plain"> </span><span class="token keyword null nil" style="color:#00009f">null</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">renderPreview</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><br></div></code></pre></div></div>
<p>Even though the two pages are completely separate contexts (DevTools panel vs. a regular extension page), simply watching the shared <code>chrome.storage</code> store is enough to get reactive synchronization for free.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="final-steps-getting-it-onto-the-store">Final Steps: Getting It Onto the Store<a href="https://blog.duduling.dev/en/hello-chrome-extension#final-steps-getting-it-onto-the-store" class="hash-link" aria-label="Direct link to Final Steps: Getting It Onto the Store" title="Direct link to Final Steps: Getting It Onto the Store" translate="no">​</a></h2>
<p>Having the feature fully working wasn't the end of it. Getting it onto the store required a few more things.</p>
<p><strong>Packaging.</strong> <code>scripts/package.sh</code> uses an <strong>allowlist</strong> rather than an exclude list. Instead of individually excluding <code>node_modules</code>, <code>.git</code>, the README, dev-only config files, etc., it explicitly lists only the files actually needed for the store.</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">VERSION=$(node -p "require('./manifest.json').version")</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">FILES=(</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  manifest.json devtools.html devtools.js</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  panel.html panel.css panel.js</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  options.html options.js theme.css</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  lib/defaults.js lib/formatter.js</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  icons/icon16.png icons/icon48.png icons/icon128.png</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">)</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">zip -X -q "$ZIP_PATH" "${FILES[@]}"</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">unzip -l "$ZIP_PATH"</span><br></div></code></pre></div></div>
<p>Files not needed at runtime, like <code>icon.svg</code> or the 512px icon, simply aren't in the list to begin with, so there's no room for them to sneak in by accident, and the final <code>unzip -l</code> gives one more visual check of what's actually included. An exclude list forces you to keep asking "should this be excluded too?" every time you add a new file, but with an allowlist, any new file just silently doesn't make it into the store bundle — much more peace of mind.</p>
<p><strong>Localization.</strong> The README and store listing copy were prepared in four languages: Korean, English, Japanese, and Chinese. The runtime UI strings themselves, however, were kept in English across the board — I judged that localization belonged in the documentation/marketing space here, not something that warranted building a whole new UI translation system. The brand name "Picky Data" wasn't translated in any language version.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="looking-back">Looking Back<a href="https://blog.duduling.dev/en/hello-chrome-extension#looking-back" class="hash-link" aria-label="Direct link to Looking Back" title="Direct link to Looking Back" translate="no">​</a></h2>
<p>I didn't expect to go from planning to store-submission-ready in just a few hours with Claude Code. I described features in prompts, loaded the unpacked extension into <code>chrome://extensions</code> to try it right away, and finished it up by adding things I hadn't originally thought of as I actually used it. Calling it "vibe coding" doesn't mean I just handed everything off and walked away, though. Curious about the internal structure and logic, I asked for commits to be split atomically, and later followed the commit history to review how the development unfolded — it was a genuinely fun experience.</p>
<p>Thanks to AI, the tedious copying I used to do every time is solved. It's not exactly a performance showcase, but it's made my own life easier while letting me hand teammates a nicely formatted Request/Response — so I got both birds with one stone. It's just a first version for now, but I plan to keep updating it as I use it and notice what's missing. It started as a "might as well build this since it's annoying" kind of thought, and before I knew it, throwing it at an AI turned it into something finished. Since I'd already built it, I figured there might be at least one person out there in the world who feels the same kind of annoyance I do, so I paid the $5 registration fee and signed up as a Google extension developer too. 💸</p>
<p>If you're interested, please give it a try — I'd love to hear your feedback, and I'll review and update it with an open mind.</p>
<a href="https://chromewebstore.google.com/detail/fhmdmdbneogkofoillfbkidhbcgibacb?utm_source=item-share-cb" target="_blank" rel="noopener noreferrer" class="rlc-container">
  <div class="rlc-info">
    <div class="rlc-title">Picky Data - Copy Network Requests - Chrome Web Store</div>
    <div class="rlc-description">Copy headers/request/response of network requests in DevTools in your preferred format.</div>
    <div class="rlc-url-container">
      <img decoding="async" loading="lazy" class="rlc-favicon img_ev3q" src="https://www.google.com/s2/favicons?domain=chromewebstore.google.com" alt="Picky Data - Copy Network Requests - Chrome Web Store favicon" width="16" height="16">
      <span class="rlc-url">https://chromewebstore.google.com/detail/fhmdmdbneogkofoillfbkidhbcgibacb?utm_source=item-share-cb</span>
    </div>
  </div>
  <div class="rlc-image-container">
      <img decoding="async" loading="lazy" class="rlc-image img_ev3q" src="https://lh3.googleusercontent.com/I4o1mvq-bJYmQM_XAol6zvUifuB3YEmZx1WIYr1XZ7EuVTK0Ziv5mvqR2pS4CJWbuBBBvg_ybmFBEF8ZRaT_v2wRSaA=s128-rj-sc0x00ffffff" alt="Picky Data - Copy Network Requests - Chrome Web Store">
    </div>
</a>]]></content:encoded>
            <category>Vibe Coding</category>
            <category>Chrome Extension</category>
        </item>
    </channel>
</rss>