{
  "id": 3587497,
  "title": "The Surprising Complexity of Injecting a UI into Someone Else's Web Page",
  "url": "https://urgent.news/2026/08/26/the-surprising-complexity-of-injecting-a-ui-into-someone-elses-web",
  "topic": "tech",
  "section": "Tech",
  "published": "2026-08-26T20:32:53.000Z",
  "source": {
    "name": "Dev.to",
    "slug": "dev-to",
    "url": "https://dev.to/parvejshah/the-surprising-complexity-of-injecting-a-ui-into-someone-elses-web-page-32p"
  },
  "original_language": "en",
  "account": "Leadswave, a LinkedIn brand assistant Chrome extension, initially seemed like a simple project to detect LinkedIn post elements in the feed and attach a small AI companion button to each. However, the team soon discovered the complexities involved in injecting the UI into a third-party web page.\n\nWhen injecting HTML into LinkedIn's document, the content script is working within a page that they don't control, with styles they didn't write and a DOM structure that can change unpredictably. The first version of Leadswave appended a styled div to each post element and mounted a React component inside it. But on the actual LinkedIn feed, Tailwind utility classes either had no effect due to LinkedIn's CSS specificity being higher, or caused unintended effects in LinkedIn's own components as their styles bled into the Leadswave DOM nodes.\n\nTo resolve this, the team turned to Shadow DOM, a modern browser feature that allows attaching a shadow root to a host element, creating an isolated DOM subtree with its own style cascade. This way, Leadswave's styles couldn't bleed into LinkedIn's components and vice versa. The mountAssistantWidget function demonstrates how to create a shadow container, attach a shadow root, add styles within the shadow, append a mount point, and finally append both the shadow and mount point to the host element.\n\nManifest V3 service workers replaced persistent background pages in Manifest V2. Service workers can be terminated by the browser at any time, which can lead to unexpected behavior when developing locally. To avoid relying on module-level variables for persistent state, the team read from storage instead. The extractPostContext function demonstrates how to extract clean context from LinkedIn's DOM by querying specific elements, extracting text nodes, and joining the text together for the AI generation process.",
  "summary": "Originally published at parvejshah.com/blog/building-manifest-v3-ai-chrome-extensions by Parvej Shah . When we started building Leadswave — a LinkedIn brand assistant Chrome extension — the plan seemed straightforward. Detect LinkedIn post elements in the feed, attach a small AI companion button to each one, let users generate engagement responses without leaving the page. A week's work, maybe…",
  "key_points": [],
  "editors_take": null,
  "illustration": null,
  "coverage": {
    "outlets": 1,
    "also_reported_by": []
  },
  "ai_generated": true,
  "disclaimer": "Summaries, key points and the editor’s take are written by software from other outlets’ reporting and may contain errors — always check the linked original."
}