<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Khaled Mahmud</title>
    <description>The latest articles on DEV Community by Khaled Mahmud (@khaled_kmp).</description>
    <link>https://dev.to/khaled_kmp</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3946440%2Ffff3be78-b9b1-44cf-86d0-bdca446d405e.jpg</url>
      <title>DEV Community: Khaled Mahmud</title>
      <link>https://dev.to/khaled_kmp</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/khaled_kmp"/>
    <language>en</language>
    <item>
      <title>Building a Clipboard Listener Chrome Extension in Manifest V3: What I Learned the Hard Way</title>
      <dc:creator>Khaled Mahmud</dc:creator>
      <pubDate>Fri, 22 May 2026 16:26:51 +0000</pubDate>
      <link>https://dev.to/khaled_kmp/building-a-clipboard-listener-chrome-extension-in-manifest-v3what-i-learned-the-hard-way-126l</link>
      <guid>https://dev.to/khaled_kmp/building-a-clipboard-listener-chrome-extension-in-manifest-v3what-i-learned-the-hard-way-126l</guid>
      <description>&lt;p&gt;When I started building ReFind — a Chrome extension that captures URLs&lt;br&gt;
as you copy them — I assumed the clipboard listener would be the easy part.&lt;br&gt;
It wasn't. Here's what actually happened and how I solved it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Goal&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Every time the user copies a URL (Cmd+C / Ctrl+C on a link), the extension&lt;br&gt;
should silently capture it, validate it's actually a URL, and process it.&lt;br&gt;
No popup interaction required. Fully automatic.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why Background Scripts Are Tricky in MV3&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Manifest V3 replaced persistent background pages with service workers.&lt;br&gt;
Unlike a background page that stays alive as long as the browser is open,&lt;br&gt;
a service worker can be terminated by Chrome after ~30 seconds of inactivity.&lt;/p&gt;

&lt;p&gt;This creates a problem: if you store anything in memory (variables, state),&lt;br&gt;
it can vanish between events. The fix: persist everything to&lt;br&gt;
chrome.storage.local immediately.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Clipboard Access Problem&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Here's the tricky part: you can't directly access navigator.clipboard&lt;br&gt;
from a service worker context. Chrome restricts clipboard access to require&lt;br&gt;
an active user gesture (a click, keypress, etc.) — and service workers&lt;br&gt;
don't have a DOM or event context for this.&lt;/p&gt;

&lt;p&gt;The solution I landed on:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Listen for the copy keyboard shortcut using chrome.commands API&lt;/li&gt;
&lt;li&gt;Inject a content script into the active tab (which has DOM access)&lt;/li&gt;
&lt;li&gt;Have the content script read the clipboard and message the
background script with the result&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Content script → background script communication via chrome.runtime.sendMessage().&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;URL Validation&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Once I had clipboard access working, I needed to filter for URLs only.&lt;br&gt;
A simple regex check before processing:&lt;/p&gt;

&lt;p&gt;const isUrl = /^https?:\/\/.+/.test(text);&lt;/p&gt;

&lt;p&gt;This prevents capturing phone numbers, random copied text, passwords, etc.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What I'd Do Differently&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If I were starting over, I'd add the URL validation earlier in the pipeline —&lt;br&gt;
ideally at the content script level before sending to the background. Less&lt;br&gt;
round-tripping, and it prevents unnecessary message passing.&lt;/p&gt;

&lt;p&gt;The one thing I underestimated: the permission model in MV3 is strict and&lt;br&gt;
intentionally so. Read the permission docs carefully before you assume&lt;br&gt;
something will just work.&lt;/p&gt;

&lt;p&gt;Questions? I'm happy to go deeper on any part of this.&lt;/p&gt;

</description>
      <category>devjournal</category>
      <category>javascript</category>
      <category>tutorial</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
