Building Cross-Platform Browser Extensions
With Svelte

Why?

const url = "https://securepubads.g.doubleclick.net/gampad/ads?...";
const searchParams = new URL(url).searchParams;
const kvPairs = Object.fromEntries(searchParams.entries());

console.log({ kvPairs })
{
    "pvsid": "2681769169518727",
    "correlator": "4500362199754403",
    "eid": "31091864,83321073,31086809,31091827",
    "output": "ldjh",
    "gdfp_req": "1",
    "vrg": "202504150101",
    "ptt": "17",
    "impl": "fif",
    "cust_params": "pt=art&nlayout=default&mvt=ads-explainer-widget%3Avariant%2Cads-pg-article%3Avariant%2Cadsmvt2%3Acontrol%2Carticle-bullet-point-summary-v2%3Abullet-points-on%2Ccomments-discussion-question-2%3Acontrol%2Cproarticlecontextwidgetabtest%3Avariant-2%2Cproask%3Aon&rootid=cm9skff5f00013b6s80o6a8hl&guid=63c4feab-5de0-4eff-8742-f8a07f8e19d7&device_spoor_id=bf2771bb-142b-444b-aa17-9ad8cf18016f&slv=int&loggedIn=true&gender=M&05=map&06=itc&07=ts&auuid=7a65a578-044a-44bf-802e-84379a77f806&title=Donald%20Trump%20has%20given%20BYD%20the%20edge%20over%20Tesla&ad=HT01%2CHT03%2CHT05%2CHT06%2CHT07%2CHT14%2CS_11831%2CS_12984%2CS_13314%2CS_13449%2CS_13748%2CS_16042%2CS_16450%2CS_16717%2CS_17165%2CS_17168%2CS_18190%2CS_19214%2CS_24494%2CS_24575%2CS_24599%2CS_2542%2CS_2547%2CS_8379%2CS_9533%2Cbs02%2Cbs50%2Ccs01%2Cft43%2Cft69%2Cft70%2Cft72%2Cr03%2Cr03a%2Cr03b%2Cr03c%2Cr03g%2Cr03l%2Cr03m%2Cr05%2Cr05b%2Cr05j%2Cr07c%2Cr07r%2Ct01&ca=car%20industry%2Celectric%20vehicle%2Cluxury%2Cpolitics&topic=Trump%20tariffs%2CUS%20companies%2CUS-China%20trade%20dispute%2CAsia-Pacific%20companies%2CElectric%20vehicles%2CChinese%20business%20%26%20finance%2CUS%20trade%2CGlobal%20trade%2CGlobal%20Economy%2CAsia-Pacific%20economy%2CTrade%20disputes%2CUS%20economy%2CWorld%2CChinese%20economy%2CCompanies%2CIndustrials%2CChinese%20trade%2CAutomobiles%2CEconomy%2CEmerging%20markets&secbrand=Business%20Insight&mvt2=control&cc=y&ts=20250422145336&res=extra&url=https%3A%2F%2Fwww.ft.com%2Fcontent%2F7a65a578-044a-44bf-802e-84379a77f806&permutive=102213%2C103258%2C117171%2C122045%2C158581%2C158583%2C158585%2C158589%2C158591%2C16780%2C16781%2C16782%2C16842%2C16843%2C17068%2C17075%2C17117%2C17124%2C17128%2C17138%2C171478%2C17173%2C17182%2C17183%2C17186%2C18238%2C18245%2C18248%2C18260%2C18263%2C18268%2C18310%2C18312%2C18314%2C18323%2C18356%2C18445%2C18731%2C18732%2C188019%2C188465%2C195959%2C197394%2C197395%2C197397%2C19902%2C20008%2C20283%2C203239%2C20431%2C20469%2C207153%2C207935%2C20833%2C209455%2C214858%2C21535%2C21541%2C217968%2C218494%2C219014%2C219015%2C219772%2C220220%2C220935%2C220949%2C23027%2C23937%2C26440%2C55914%2C55934%2C56089%2C56090%2C66495%2C73402%2C74657%2C74659%2C74661%2C74664%2C74665%2C86173%2C87696%2C87738%2C87739%2C87740%2C87741%2C87742%2C87744%2C88113%2C92621%2C92622%2C99536%2Cadv%2Cbrxc%2Crts&permutive-id=e78042f1-83ed-4378-883f-f9d95aee4983&prmtvsdk=web&puid=e78042f1-83ed-4378-883f-f9d95aee4983&prmtvvid=a87e2d27-9457-4017-985a-f9efe43d0a3d&prmtvsid=2d4867c9-5e0d-4984-82db-47f34b3e3799&prmtvwid=e1c3fd73-dd41-4abd-b80b-4278d52bf7aa&fr=false&adt=veryLow&alc=veryLow&dlm=veryLow&drg=veryLow&hat=veryLow&off=veryLow&vio=veryLow&ias-kw=IAS_1516717_PG%2CIAS_1517168_PG%2CIAS_1517165_PG%2CIAS_1516042_PG%2CIAS_1516450_PG%2CIAS_3010239_PG%2CIAS_3007857_PG%2CIAS_3010449_PG%2CIAS_3008527_PG%2CIAS_1509533_PG%2CIAS_1510285_PG%2CIAS_1500942_PG%2CIAS_1513314_PG%2CIAS_1513748_PG%2CIAS_1513449_PG%2CIAS_3005022_PG%2CIAS_1500902_PG%2CIAS_1511831_PG%2CIAS_3006643_PG%2CIAS_1512984_PG%2CIAS_1519214_PG%2CIAS_1518190_PG%2CIAS_3008351_PG&view-id=",
    "gdpr_consent": "CQN2PcAQN2PcAAGABCENBfFgAPPAAELAAB6YH9wGAAGAAVABkAEeAJoAnABdADcAH4AQIA7gB7AEIAIsATsArIBaQC6gGmAROAvMBkgDfgH1AP7A36ASAD8AI4AfgB3AEIAIsAvMBtADfgBQkAUACoAMgB3AD2AZIOgEAAVABkAF0AdwA9gETgMkAf2QgBgDuAROA_slABAZIUgDgAVABkAH4AiwCJwGSAP7.YAAACFgAAAAA",
    "gdpr": "1",
    "addtl_consent": "1~196.2072",
    "us_privacy": "1---",
    "gpp_sid": "-1",
    "iu_parts": "5887,ft.com,companies,us.and.canada",
    "enc_prev_ius": "/0/1/2/3",
    "prev_iu_szs": "1x1",
    "ifi": "3",
    "dids": "miniBillboard-gpt",
    "adfs": "1334086546",
    "sfv": "1-0-41",
    "eri": "4",
    "sc": "1",
    "cookie": "ID=c1f1376e4506574f:T=1744816867:RT=1745330016:S=ALNI_MZSK9sjZkrzAXIcnnGT-rSsqnm_Vw",
    "abxe": "1",
    "dt": "1745330026828",
    "lmt": "1745330026",
    "adxs": "-12245933",
    "adys": "-12245933",
    "biw": "1446",
    "bih": "597",
    "scr_x": "0",
    "scr_y": "0",
    "btvi": "-1",
    "ucis": "3",
    "oid": "2",
    "u_his": "7",
    "u_h": "1117",
    "u_w": "1728",
    "u_ah": "1079",
    "u_aw": "1728",
    "u_cd": "30",
    "u_sd": "2",
    "u_tz": "60",
    "dmc": "8",
    "bc": "31",
    "nvt": "3",
    "uach": "WyJtYWNPUyIsIjE1LjQuMSIsImFybSIsIiIsIjEzNC4wLjY5OTguMjA3IixudWxsLDAsbnVsbCwiNjQiLFtbIkNocm9taXVtIiwiMTM0LjAuNjk5OC4yMDciXSxbIk5vdDpBLUJyYW5kIiwiMjQuMC4wLjAiXSxbIkdvb2dsZSBDaHJvbWUiLCIxMzQuMC42OTk4LjIwNyJdXSwwXQ..",
    "uas": "1",
    "url": "https://www.ft.com/content/7a65a578-044a-44bf-802e-84379a77f806",
    "ref": "https://www.ft.com/content/b4c868aa-28e7-4f35-8357-88e59a4fa9c2",
    "vis": "1",
    "psz": "0x0",
    "msz": "0x0",
    "fws": "128",
    "ohw": "0",
    "psts": "AOrYGslr7db69t4c-MKr7kBkANY2CFjg3Q4RlRawsMCPezFh8rtHnZIME_UXWHHphJf0kPRWY6olj97cSvDEir8d4SYpe0_iWMT8aEcQ0zRNrQ,AOrYGslN2gGiIH-zOM_qk9IFLC4vZ56xUNNiyotldVx4fflW9b8sz8cHeX7n4xS1EZ1tYk38DXnSly1dtVTtq8c_eXA",
    "nt": "1",
    "psd": "WzI3LFtdLG51bGwsM10.",
    "dlt": "1745330015695",
    "idt": "465",
    "ppid": "bf2771bb-142b-444b-aa17-9ad8cf18016f",
    "prev_scp": "pos=top&id=2fe208bd-1f81-11f0-8113-7ec3216436b3",
    "adks": "3361870855",
    "frm": "20",
    "eo_id_str": "ID=f9ed9fb12ffe8b57:T=1744816867:RT=1745330016:S=AA-AfjZaxjpAZumuzXJbxfLP-zvm",
    "td": "1",
    "tan": "bfda6740-2e58-48b5-839f-08ee83e200e0",
    "tdf": "2"
}
const url = "https://securepubads.g.doubleclick.net/gampad/ads?...";
const searchParams = new URL(url).searchParams;
const kvPairs = Object.fromEntries(searchParams.entries());

const custParamsRaw = new URLSearchParams(kvPairs.cust_params);
const custParams = Object.fromEntries(custParamsRaw.entries());

console.log({ custParams })
{
    "pt": "art",
    "nlayout": "default",
    "mvt": "ads-explainer-widget:variant,ads-pg-article:variant,adsmvt2:control,article-bullet-point-summary-v2:bullet-points-on,comments-discussion-question-2:control,proarticlecontextwidgetabtest:variant-2,proask:on",
    "rootid": "cm9slla3200003b7i3wzpq83z",
    "guid": "63c4feab-5de0-4eff-8742-f8a07f8e19d7",
    "device_spoor_id": "bf2771bb-142b-444b-aa17-9ad8cf18016f",
    "slv": "int",
    "loggedIn": "true",
    "gender": "M",
    "05": "map",
    "06": "itc",
    "07": "ts",
    "adt": "veryLow",
    "alc": "veryLow",
    "dlm": "veryLow",
    "drg": "veryLow",
    "hat": "veryLow",
    "off": "veryLow",
    "vio": "medium",
    "auuid": "77a70ee0-6646-4d2c-97ba-d51386605f63",
    "title": "Ukraine accuses Russia of fresh attacks despite Putin ceasefire",
    "ad": "HT11,S_11831,S_12565,S_12984,S_13314,S_13449,S_13748,S_15033,S_15973,S_16011,S_16042,S_16417,S_16420,S_16450,S_16717,S_17165,S_17168,S_18190,S_19214,S_24494,S_24575,S_24599,S_7033,S_9089,S_9100,S_9533,bs01,bs02,bs03,bs24,bs28,bs50,e2,ft57,ft70,r03,r03c,r03g,r03m,r05c,r05k,t01",
    "ca": "foreign policy,unrest and war",
    "topic": "Russian politics,War in Ukraine,Politics,World,Emerging markets",
    "mvt2": "control",
    "cc": "y",
    "ts": "20250422152609",
    "res": "extra",
    "url": "https://www.ft.com/content/77a70ee0-6646-4d2c-97ba-d51386605f63",
    "permutive": "102213,103258,117171,122045,158581,158583,158585,158589,158591,16780,16781,16782,16842,16843,17068,17075,17117,17124,17128,17138,171478,17173,17182,17183,17186,18238,18245,18248,18260,18263,18268,18310,18312,18314,18323,18356,18445,18731,18732,188019,188465,195959,197394,197395,197397,19902,20008,20283,203239,20431,20469,207153,207935,20833,209455,214858,21535,21541,217968,218494,219014,219015,219772,220220,220935,220949,23027,23937,26440,55914,55934,56089,56090,66495,73402,74657,74659,74661,74664,74665,86173,87696,87738,87739,87740,87741,87742,87744,88113,92621,92622,99536,adv,brxc,rts",
    "permutive-id": "e78042f1-83ed-4378-883f-f9d95aee4983",
    "prmtvsdk": "web",
    "puid": "e78042f1-83ed-4378-883f-f9d95aee4983",
    "prmtvvid": "0ca431d9-8b43-43e5-9dc3-92da5f2444ba",
    "prmtvsid": "2d4867c9-5e0d-4984-82db-47f34b3e3799",
    "prmtvwid": "e1c3fd73-dd41-4abd-b80b-4278d52bf7aa",
    "fr": "false",
    "ias-kw": "IAS_1516420_PG,IAS_1503483_PG,IAS_1516011_PG,IAS_1517165_PG,IAS_1516042_PG,IAS_3010239_PG,IAS_1509533_PG,IAS_1513449_PG,IAS_1500903_PG,IAS_1511831_PG,IAS_1508970_PG,IAS_1518190_PG,IAS_1519214_PG,IAS_1516717_PG,IAS_1512565_PG,IAS_1517168_PG,IAS_1507080_PG,IAS_1511377_PG,IAS_1516450_PG,IAS_1516417_PG,IAS_1503195_PG,IAS_1509981_PG,IAS_1515973_PG,IAS_1500942_PG,IAS_1513314_PG,IAS_1510285_PG,IAS_1513748_PG,IAS_1503224_PG,IAS_1503222_PG,IAS_1500902_PG,IAS_1507653_PG,IAS_1512984_PG,IAS_1509089_PG,IAS_1509100_PG"
}

https://developer.chrome.com/docs/extensions

https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions

https://developer.mozilla.org/en-US/docs/Mozilla/
Add-ons/WebExtensions/Chrome_incompatibilities

https://github.com/mdn/webextensions-examples

https://github.com/GoogleChrome/chrome-extensions-samples


What are content scripts?

https://youtu.be/ezhJezGX5ak

😤

// manifest.json
{
  "name": "Hello Extensions",
  "description": "Base Level Extension",
  "version": "1.0",
  "manifest_version": 3,
  "action": {
    "default_popup": "hello.html",
    "default_icon": "hello_extensions.png"
  }
}

Tooling

https://www.plasmo.com/

https://crxjs.dev/vite-plugin

https://vite-plugin-web-extension.aklinker1.io

https://wxt.dev

https://wxt.dev/examples.html

https://wxt.dev/guide/resources/compare.html

├── .vscode
│   └── extensions.json
├── src
│   ├── assets
│   │   └── svelte.svg
│   ├── entrypoints
│   │   ├── popup
│   │   │   ├── app.css
│   │   │   ├── App.svelte
│   │   │   ├── index.html
│   │   │   └── main.ts
│   │   ├── background.ts
│   │   └── content.ts
│   └── lib
│       └── Counter.svelte
├── .gitignore
├── package.json
├── README.md
├── tsconfig.json
├── wxt-env.d.ts
└── wxt.config.ts
// manifest.json
{
  "name": "Hello Extensions",
  "description": "Base Level Extension",
  "version": "1.0",
  "manifest_version": 3,
  "action": {
    "default_popup": "hello.html",
    "default_icon": "hello_extensions.png"
  }
}
import { defineConfig } from 'wxt';

export default defineConfig({
  srcDir: 'src',
  modules: ['@wxt-dev/module-svelte'],
});
// wxt.config.ts
export default defineConfig({
	srcDir: 'src',
	modules: ['@wxt-dev/module-svelte'],
    
  	svelte: {
		vite: {
			inspector: import.meta.env.NODE_ENV === "development",
		},
	},
});
npm i -D @sveltejs/vite-plugin-svelte-inspector

🫙

DEV TOOLS

PAGE

📄

📄

📄

POP-UP

// injected.ts
function onSomePageEvent() {
	document.dispatchEvent(
		new CustomEvent("myExtension:somePageEvent", { 
          detail: { ... }
        }),
	);
}
// content.ts
export default defineContentScript({
	async main(ctx) {
      storageA = storage.defineItem("session:storageA", {fallback: {}});
      
      ctx.addEventListener(document, 
        "myExtension:somePageEvent", 
        async ({ detail }) => {
          await Promise.all([
            storageA.setValue(detail.propA),
            storageB.setValue(detail.propB),
          ]);
      });
	},
});
<script lang="ts">
	const myState = $state<MyState>({ ... })
  
    storageA.getValue().then(updateMyState);
	storageA.watch(updateMyState);
</script>

https://github.com/oliverturner

🦋 oliverturner.cloud

Made with Slides.com