Skip to content

feat: hotstar new app & netflix pip enable#17

Merged
WINOFFRG merged 1 commit intomasterfrom
feat/0603
Mar 6, 2026
Merged

feat: hotstar new app & netflix pip enable#17
WINOFFRG merged 1 commit intomasterfrom
feat/0603

Conversation

@WINOFFRG
Copy link
Copy Markdown
Owner

@WINOFFRG WINOFFRG commented Mar 6, 2026

Summary by CodeRabbit

  • New Features

    • Added auto Picture-in-Picture mode support for Netflix
    • Integrated Hotstar streaming service with built-in ad blocking and telemetry protection
  • Chores

    • Updated build tool dependency
    • Added browser permissions for enhanced features

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Mar 6, 2026

📝 Walkthrough

Walkthrough

This PR adds support for a new Hotstar app with middleware-based ad-blocking and telemetry-disabling rules, introduces auto picture-in-picture functionality for Netflix, implements a rule support system for conditional feature-based rule enabling, extends DNR rules for Hotstar, and enhances the fetch polyfill with header tracking and response mocking support.

Changes

Cohort / File(s) Summary
Changeset Metadata
.changeset/tender-heads-marry.md, .changeset/thirty-webs-travel.md
Two new changeset files documenting minor releases for Hotstar PIP enabling and Hotstar new app features.
Hotstar App Integration
src/lib/apps/hotstar/config.ts, src/lib/apps/hotstar/block-ads.ts, src/lib/apps/hotstar/disable-telemetry.ts, src/lib/apps/hotstar/middleware.ts
New Hotstar app configuration with middleware for ad-widget removal from BFF responses, telemetry disable placeholder, and header normalization (x-hs-client platform spoofing). Includes response interceptor logic with JSON parsing and targeted payload transformation.
Hotstar Registry & UI
src/lib/apps/registry.ts, src/lib/apps/ui-config.ts
Registers Hotstar app config in app registry and adds UI styling/theming for Hotstar cards with gradient and variant configuration.
Netflix Auto Picture-in-Picture
src/lib/apps/netflix/picture-in-picture.ts, src/lib/apps/netflix/auto-picture-in-picture.ts, src/lib/apps/netflix/config.ts
New feature for Netflix: detects Picture-in-Picture support, patches video elements to enable autopictureinpicture, integrates into Netflix config with rule ID and initialization hook.
Rule Support Infrastructure
src/lib/rule-support.ts, src/lib/shared/types.ts
New rule support system with feature detection callbacks and config enrichment. Adds unsupportedDescription and supported optional fields to AppRule interface for conditional rule display.
Telemetry & Network Control
src/lib/dnr-rules.ts
Extends DNR rules with 15 new Hotstar-scoped telemetry-blocking rules using initiatorDomains constraint. Adds initiatorDomains optional field to DnrRule condition interface.
Content & Component Updates
src/entrypoints/content/index.tsx, src/components/RuleItem.tsx, src/components/OTTModal.tsx
Enriches app configs with rule support flags using withRuleSupport, updates RuleItem to accept and respect a supported prop (disables toggle, shows unsupported description), and propagates support status from OTTModal to RuleItem.
Fetch Polyfill Enhancement
src/lib/fetch-polyfill.ts
Adds HTTP request header capture (via xhr.setRequestHeader override) and propagation to middleware context, supports mocked Response handling with readyState/status synchronization and event dispatching for XHR flows.
Manifest & Configuration
.gitignore, package.json, wxt.config.ts
Removes Hotstar from gitignore (enabling source control), bumps wxt to 0.20.18, adds publish-browser-extension override, and expands host_permissions for analytics/tracking domains.

Sequence Diagrams

sequenceDiagram
    participant Client as Request
    participant Middleware as blockAds Middleware
    participant Parser as JSON Parser
    participant Remover as Payload Transform
    participant Response as Response Patch
    
    Client->>Middleware: Intercept /api/internal/bff/v2/
    Middleware->>Parser: Parse response JSON
    Parser-->>Middleware: Parsed payload
    Middleware->>Remover: removeDisplayAdWidgets()
    Middleware->>Remover: removePlayerAdData()
    Remover-->>Middleware: Ads removed, indices recorded
    Middleware->>Response: Patch response if removals occurred
    Response-->>Client: Updated response with ads stripped
Loading
sequenceDiagram
    participant Content as Content Script
    participant RuleSupport as withRuleSupport
    participant FeatureDetect as isRuleSupported
    participant Config as App Config
    participant UI as UI (RuleItem)
    
    Content->>RuleSupport: Enrich app configs
    RuleSupport->>FeatureDetect: Check Netflix PiP support
    FeatureDetect-->>RuleSupport: boolean (device capability)
    RuleSupport->>Config: Attach supported flag to rules
    Config-->>UI: Pass enriched config
    UI->>UI: Disable rule if supported=false
    UI-->>UI: Show unsupportedDescription if disabled
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Poem

🐰 A Hotstar hop, a Netflix leap so high,
With ads blocked clean and PiP reaching the sky,
Rules now aware of what devices can do,
Headers transform and fetch finds something new—
Feature-rich magic, hopping ever true! ✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the two main changes: adding Hotstar as a new app and enabling Netflix PiP feature.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/0603

Tip

Try Coding Plans. Let us write the prompt for your AI agent so you can ship faster (with fewer bugs).
Share your feedback on Discord.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@WINOFFRG
Copy link
Copy Markdown
Owner Author

WINOFFRG commented Mar 6, 2026

@coderabbitai help

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Mar 6, 2026

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

CodeRabbit commands

These commands are invoked using PR/Issue comments.

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai evaluate custom pre-merge check --instructions <custom-checks-instructions> --name <custom-checks-title> [--mode <error|warning>] to validate the custom pre-merge checks instructions. Defaults to error when --mode is omitted.
    • @coderabbitai ignore pre-merge checks to override pre-merge checks and get an approval on PR.
    • @coderabbitai run pre-merge checks to run pre-merge checks on the pull request.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai generate unit tests to generate unit tests for this PR.
  • @coderabbitai resolve to resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai generate configuration to create a PR that adds the current resolved configuration as .coderabbit.yaml (or show it if already present).
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore or @coderabbit ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit configuration file (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • You can also validate your configuration using the online YAML validator.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, support, documentation and community

  • Visit our status page to check the current availability of CodeRabbit.
  • Create a ticket on our support page for assistance with any issues or questions.
  • Visit our documentation site for detailed information on how to use CodeRabbit.
  • Join our Discord community to connect with other users and get help from the community.
  • Follow us on X/Twitter for updates and announcements.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 7

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/lib/fetch-polyfill.ts (1)

72-94: ⚠️ Potential issue | 🟠 Major

XHR header rewrites stop at ctx.init and never reach the real request.

These lines capture headers for middleware, but they still call originalSetRequestHeader() before runMiddlewares(ctx). Any middleware update to ctx.init.headers is ignored on pass-through XHRs, and the captured header set also survives across open() calls on a reused XHR instance.

💡 Suggested direction
-    const requestHeaders = new Headers();
+    let requestHeaders = new Headers();

     xhr.open = function (
       _method: string,
       _url: string | URL,
       async?: boolean,
       username?: string | null,
       password?: string | null,
     ) {
       method = _method;
       url = _url.toString();
+      requestHeaders = new Headers();
       return originalOpen(_method, _url, async ?? true, username, password);
     };

     xhr.setRequestHeader = function (name: string, value: string) {
-      requestHeaders.set(name, value);
-      return originalSetRequestHeader(name, value);
+      requestHeaders.append(name, value);
     };

     xhr.send = async function (
       body?: Document | XMLHttpRequestBodyInit | null,
     ) {
       requestBody = body;

       logger.debug("🎯 Processing XHR for:", url);

       const ctx: MiddlewareContext = {
         request: url,
         init: {
           method,
           body: body as BodyInit | undefined,
           headers: requestHeaders,
         },
         url,
         handled: false,
         setHandled() {
           this.handled = true;
         },
         setResponse(resp: Response) {
           this.response = resp;
         },
         response: undefined,
         originalFetch: originalFetch.bind(window),
       };

       await runMiddlewares(ctx);
+
+      for (const [name, value] of new Headers(
+        ctx.init?.headers ?? requestHeaders,
+      ).entries()) {
+        originalSetRequestHeader(name, value);
+      }

       if (ctx.handled && ctx.response) {

Also applies to: 103-108

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/lib/fetch-polyfill.ts` around lines 72 - 94, The override of
xhr.setRequestHeader calls originalSetRequestHeader immediately (using
originalSetRequestHeader) so middleware changes to ctx.init.headers never get
applied and captured requestHeaders persist across reused XHRs; fix by resetting
method, url, requestBody and requestHeaders inside xhr.open (clear previous
state), change xhr.setRequestHeader to only mutate the local requestHeaders (do
NOT call originalSetRequestHeader there), and move the calls to
originalSetRequestHeader into the send path (inside xhr.send where
runMiddlewares(ctx) is executed): after runMiddlewares(ctx) merge requestHeaders
with ctx.init.headers and then iterate those headers to call
originalSetRequestHeader before invoking originalSend; reference the symbols
xhr.open, xhr.setRequestHeader, originalSetRequestHeader, requestHeaders,
requestBody, method, url, runMiddlewares, xhr.send, and originalSend.
🧹 Nitpick comments (1)
src/lib/rule-support.ts (1)

18-20: Preserve config-authored support flags when annotating rules.

withRuleSupport() currently replaces any existing rule.supported value. That makes a statically unsupported rule render as supported unless isRuleSupported() happens to special-case it, which also bypasses the unsupportedDescription path in src/components/OTTModal.tsx.

Suggested change
     rules: app.rules.map((rule) => ({
       ...rule,
-      supported: isRuleSupported(app.id, rule.id),
+      supported:
+        rule.supported !== false && isRuleSupported(app.id, rule.id),
     })),
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/lib/rule-support.ts` around lines 18 - 20, withRuleSupport currently
overwrites any existing rule.supported flag; change the mapping in
withRuleSupport (the app.rules.map callback) to only assign supported when the
rule doesn’t already define it (e.g. use rule.supported === undefined ?
isRuleSupported(app.id, rule.id) : rule.supported), leaving
unsupportedDescription and any existing support flags intact so static
“unsupported” metadata still flows through to OTTModal.tsx; do this by updating
the mapping that references isRuleSupported and rule.supported rather than
unconditionally replacing rule.supported.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/lib/apps/hotstar/block-ads.ts`:
- Around line 168-170: The current code calls ctx.originalFetch(ctx.request,
ctx.init) and then on later errors falls back to await next(), which can replay
the request; instead preserve and return the original Response when patching
fails: after successfully awaiting ctx.originalFetch and storing const response,
avoid calling await next() on subsequent failures—catch patching/parsing errors,
log or handle them, and return the previously saved response; apply the same
change for the second occurrence handling the fetch at the block that spans the
other affected section (the code that clones
response.toString()/response.clone().text() and later may call next()) so no
successful fetch is re-sent via next().

In `@src/lib/apps/hotstar/middleware.ts`:
- Around line 3-33: The hotstarHeaderMiddleware export is not registered in the
Hotstar app config so it never runs; update the Hotstar configuration where
middlewares are registered (the same place that currently registers blockAds and
disableTelemetry) to import hotstarHeaderMiddleware and add it to the middleware
list/array so it gets executed for requests (ensure ordering if needed so
hotstarHeaderMiddleware runs before any middleware that might short-circuit
requests).

In `@src/lib/apps/netflix/auto-picture-in-picture.ts`:
- Around line 9-49: The current code in startAutoPictureInPicturePatch
disconnects the MutationObserver after the first successful call to
patchAutoPictureInPictureOnSingleVideo, which prevents re-patching when Netflix
swaps video elements; instead, keep the observer running so
patchAutoPictureInPictureOnSingleVideo is invoked on subsequent mutations
(remove the observer.disconnect() call in the MutationObserver callback) and
optionally add a lightweight debounce/throttle inside
startAutoPictureInPicturePatch or the observer callback to limit frequency;
refer to the observer variable and the functions startAutoPictureInPicturePatch
and patchAutoPictureInPictureOnSingleVideo when making the change.

In `@src/lib/apps/netflix/config.ts`:
- Around line 31-32: The current binding of startAutoPictureInPicturePatch to
AppRule.onInit creates a MutationObserver on document.documentElement with no
teardown path; instead, remove calling startAutoPictureInPicturePatch from
onInit and either (A) move its invocation into autoPictureInPictureMode (the
middleware) where you can create the observer only when a video page is active
and disconnect it when the middleware tears down, or (B) change
startAutoPictureInPicturePatch to return a disposer (a function that disconnects
the observer) and register that disposer with the rule's teardown lifecycle;
update the config to stop wiring startAutoPictureInPicturePatch directly to
onInit so the observer is always disconnected when the rule/middleware is
stopped.

In `@src/lib/dnr-rules.ts`:
- Around line 233-241: The telemetry rule(s) (e.g., the rule with id 1111 that
uses HOTSTAR_INITIATOR_DOMAINS and TELEMETRY_RESOURCE_TYPES) incorrectly block
generic redirect/navigation hosts like "||t.co/" and "||www.google.co.in", which
breaks non-telemetry flows; fix by removing those generic domain urlFilters from
telemetry rules and either (a) narrow each urlFilter to specific telemetry
collector paths/parameters for Hotstar, or (b) move the generic domain blocks
into a separate navigation/blocking rule set (not the Disable Telemetry rules).
Ensure changes reference the rule entries that include HOTSTAR_INITIATOR_DOMAINS
and TELEMETRY_RESOURCE_TYPES so only telemetry-specific endpoints remain
blocked.
- Around line 126-130: HOTSTAR_INITIATOR_DOMAINS is too narrow and should use
the same origin pattern as the Hotstar app config: replace the hard-coded
["www.hotstar.com"] with the pattern "(^|\\.)hotstar\\.com$" (or the equivalent
RegExp/string used in src/lib/apps/hotstar/config.ts) so DNR rules match
hotstar.com and all subdomains; also remove or narrow the generic Google block
(the rule that blocks "||www.google.co.in/") and instead block only the specific
telemetry host(s) (e.g., analytics.google.com) to avoid breaking legitimate
Google navigation.

In `@wxt.config.ts`:
- Around line 18-31: Remove the third-party host permissions listed in the host
permissions array in wxt.config.ts that are only used for DNR blocking (the
entries for analytics.google.com, www.googletagmanager.com,
connect.facebook.net, static.ads-twitter.com, bat.bing.com, a.quora.com, t.co,
usersvc.hotstar.com, www.google-analytics.com, www.google.co.in,
*.doubleclick.net, *.ingest.sentry.io, q.quora.com, cdn.growthbook.io) and
instead rely on the DNR blocking rules in src/lib/dnr-rules.ts; also remove the
duplicate usersvc.hotstar.com entry since it is covered by the existing
*.hotstar.com wildcard. Ensure no other code references these hosts before
removing them.

---

Outside diff comments:
In `@src/lib/fetch-polyfill.ts`:
- Around line 72-94: The override of xhr.setRequestHeader calls
originalSetRequestHeader immediately (using originalSetRequestHeader) so
middleware changes to ctx.init.headers never get applied and captured
requestHeaders persist across reused XHRs; fix by resetting method, url,
requestBody and requestHeaders inside xhr.open (clear previous state), change
xhr.setRequestHeader to only mutate the local requestHeaders (do NOT call
originalSetRequestHeader there), and move the calls to originalSetRequestHeader
into the send path (inside xhr.send where runMiddlewares(ctx) is executed):
after runMiddlewares(ctx) merge requestHeaders with ctx.init.headers and then
iterate those headers to call originalSetRequestHeader before invoking
originalSend; reference the symbols xhr.open, xhr.setRequestHeader,
originalSetRequestHeader, requestHeaders, requestBody, method, url,
runMiddlewares, xhr.send, and originalSend.

---

Nitpick comments:
In `@src/lib/rule-support.ts`:
- Around line 18-20: withRuleSupport currently overwrites any existing
rule.supported flag; change the mapping in withRuleSupport (the app.rules.map
callback) to only assign supported when the rule doesn’t already define it (e.g.
use rule.supported === undefined ? isRuleSupported(app.id, rule.id) :
rule.supported), leaving unsupportedDescription and any existing support flags
intact so static “unsupported” metadata still flows through to OTTModal.tsx; do
this by updating the mapping that references isRuleSupported and rule.supported
rather than unconditionally replacing rule.supported.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 6219b54e-c071-4093-8fdb-a6082879bfdd

📥 Commits

Reviewing files that changed from the base of the PR and between d175b49 and f903ad4.

⛔ Files ignored due to path filters (1)
  • bun.lock is excluded by !**/*.lock
📒 Files selected for processing (21)
  • .changeset/tender-heads-marry.md
  • .changeset/thirty-webs-travel.md
  • .gitignore
  • package.json
  • src/components/OTTModal.tsx
  • src/components/RuleItem.tsx
  • src/entrypoints/content/index.tsx
  • src/lib/apps/hotstar/block-ads.ts
  • src/lib/apps/hotstar/config.ts
  • src/lib/apps/hotstar/disable-telemetry.ts
  • src/lib/apps/hotstar/middleware.ts
  • src/lib/apps/netflix/auto-picture-in-picture.ts
  • src/lib/apps/netflix/config.ts
  • src/lib/apps/netflix/picture-in-picture.ts
  • src/lib/apps/registry.ts
  • src/lib/apps/ui-config.ts
  • src/lib/dnr-rules.ts
  • src/lib/fetch-polyfill.ts
  • src/lib/rule-support.ts
  • src/lib/shared/types.ts
  • wxt.config.ts
💤 Files with no reviewable changes (1)
  • .gitignore

Comment thread src/lib/apps/hotstar/block-ads.ts
Comment on lines +3 to +33
export const hotstarHeaderMiddleware: Middleware = async (ctx, next) => {
const HEADER_NAME = "x-hs-client";
const PATTERN_TO_REPLACE = /platform:web/g;
const REPLACEMENT_VALUE = "platform:android";

if (!ctx.url.includes("hotstar.com")) {
await next();
return;
}

let headers: Headers | undefined;
if (ctx.init?.headers instanceof Headers) {
headers = ctx.init.headers;
} else if (ctx.init?.headers && typeof ctx.init.headers === "object") {
headers = new Headers(ctx.init.headers as Record<string, string>);
}

if (headers?.has(HEADER_NAME)) {
const originalValue = headers.get(HEADER_NAME) || "";
const modifiedValue = originalValue.replace(
PATTERN_TO_REPLACE,
REPLACEMENT_VALUE
);
if (originalValue !== modifiedValue) {
headers.set(HEADER_NAME, modifiedValue);
ctx.init = { ...ctx.init, headers };
}
}

await next();
};
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Wire this middleware into the Hotstar config or it never runs.

The current Hotstar config only registers blockAds and disableTelemetry, so this export stays inert and x-hs-client is never rewritten to platform:android.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/lib/apps/hotstar/middleware.ts` around lines 3 - 33, The
hotstarHeaderMiddleware export is not registered in the Hotstar app config so it
never runs; update the Hotstar configuration where middlewares are registered
(the same place that currently registers blockAds and disableTelemetry) to
import hotstarHeaderMiddleware and add it to the middleware list/array so it
gets executed for requests (ensure ordering if needed so hotstarHeaderMiddleware
runs before any middleware that might short-circuit requests).

Comment on lines +9 to +49
function patchAutoPictureInPictureOnSingleVideo(): boolean {
const videos = document.querySelectorAll("video");
if (videos.length !== 1) {
return false;
}

const video = videos[0] as AutoPictureInPictureVideo;
video.removeAttribute("disablepictureinpicture");
video.setAttribute("autopictureinpicture", "");

if ("autoPictureInPicture" in video) {
video.autoPictureInPicture = true;
}

logger.info("[Netflix] Enabled automatic Picture-in-Picture on video", {
source: "netflix",
middleware: "auto-picture-in-picture",
});

return true;
}

export function startAutoPictureInPicturePatch() {
if (!isAutoPictureInPictureSupported()) {
return;
}

if (patchAutoPictureInPictureOnSingleVideo()) {
return;
}

const observer = new MutationObserver(() => {
if (patchAutoPictureInPictureOnSingleVideo()) {
observer.disconnect();
}
});

observer.observe(document.documentElement, {
childList: true,
subtree: true,
});
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Keep patching across video swaps instead of disconnecting after the first success.

src/entrypoints/script.ts:37-41 only calls onInit() once. After observer.disconnect() runs here, any later <video> replacement on episode/title navigation will never get patched. The inverse case is also rough: pages that never reach videos.length === 1 keep a full-document observer alive until unload.

Suggested change
+const patchedVideos = new WeakSet<HTMLVideoElement>();
+
 function patchAutoPictureInPictureOnSingleVideo(): boolean {
   const videos = document.querySelectorAll("video");
   if (videos.length !== 1) {
     return false;
   }
 
   const video = videos[0] as AutoPictureInPictureVideo;
+  if (patchedVideos.has(video)) {
+    return true;
+  }
+
   video.removeAttribute("disablepictureinpicture");
   video.setAttribute("autopictureinpicture", "");
 
   if ("autoPictureInPicture" in video) {
     video.autoPictureInPicture = true;
   }
+
+  patchedVideos.add(video);
 
   logger.info("[Netflix] Enabled automatic Picture-in-Picture on video", {
     source: "netflix",
     middleware: "auto-picture-in-picture",
   });
@@
-  const observer = new MutationObserver(() => {
-    if (patchAutoPictureInPictureOnSingleVideo()) {
-      observer.disconnect();
-    }
-  });
+  const observer = new MutationObserver(() => {
+    patchAutoPictureInPictureOnSingleVideo();
+  });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/lib/apps/netflix/auto-picture-in-picture.ts` around lines 9 - 49, The
current code in startAutoPictureInPicturePatch disconnects the MutationObserver
after the first successful call to patchAutoPictureInPictureOnSingleVideo, which
prevents re-patching when Netflix swaps video elements; instead, keep the
observer running so patchAutoPictureInPictureOnSingleVideo is invoked on
subsequent mutations (remove the observer.disconnect() call in the
MutationObserver callback) and optionally add a lightweight debounce/throttle
inside startAutoPictureInPicturePatch or the observer callback to limit
frequency; refer to the observer variable and the functions
startAutoPictureInPicturePatch and patchAutoPictureInPictureOnSingleVideo when
making the change.

Comment on lines +31 to +32
middleware: autoPictureInPictureMode,
onInit: startAutoPictureInPicturePatch,
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Avoid binding a non-disposable DOM observer to onInit.

Line 32 wires startAutoPictureInPicturePatch into onInit, but that helper creates a MutationObserver on document.documentElement and only disconnects after the patch succeeds (src/lib/apps/netflix/auto-picture-in-picture.ts:31-50). AppRule.onInit is () => void, so there is no teardown path if the user never reaches a single-video page or disables the rule later. That leaves a tab-wide observer running for the lifetime of the document.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/lib/apps/netflix/config.ts` around lines 31 - 32, The current binding of
startAutoPictureInPicturePatch to AppRule.onInit creates a MutationObserver on
document.documentElement with no teardown path; instead, remove calling
startAutoPictureInPicturePatch from onInit and either (A) move its invocation
into autoPictureInPictureMode (the middleware) where you can create the observer
only when a video page is active and disconnect it when the middleware tears
down, or (B) change startAutoPictureInPicturePatch to return a disposer (a
function that disconnects the observer) and register that disposer with the
rule's teardown lifecycle; update the config to stop wiring
startAutoPictureInPicturePatch directly to onInit so the observer is always
disconnected when the rule/middleware is stopped.

Comment thread src/lib/dnr-rules.ts
Comment thread src/lib/dnr-rules.ts
Comment on lines +233 to +241
{
id: 1111,
priority: 1,
action: { type: "block" },
condition: {
urlFilter: "||t.co/",
initiatorDomains: HOTSTAR_INITIATOR_DOMAINS,
resourceTypes: TELEMETRY_RESOURCE_TYPES,
},
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Don't block generic redirect/search hosts in a telemetry rule.

Lines 238 and 278 block t.co and www.google.co.in, which are general navigation endpoints, not telemetry-specific collectors. Shipping them under “Disable Telemetry” can break share/help/login flows initiated from Hotstar even though the user only opted into telemetry blocking.

✂️ Suggested change
   {
-    id: 1111,
-    priority: 1,
-    action: { type: "block" },
-    condition: {
-      urlFilter: "||t.co/",
-      initiatorDomains: HOTSTAR_INITIATOR_DOMAINS,
-      resourceTypes: TELEMETRY_RESOURCE_TYPES,
-    },
-  },
-  {
     id: 1112,
     priority: 1,
     action: { type: "block" },
@@
   {
-    id: 1115,
-    priority: 1,
-    action: { type: "block" },
-    condition: {
-      urlFilter: "||www.google.co.in/",
-      initiatorDomains: HOTSTAR_INITIATOR_DOMAINS,
-      resourceTypes: TELEMETRY_RESOURCE_TYPES,
-    },
-  },
-];
+];

Also applies to: 273-281

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/lib/dnr-rules.ts` around lines 233 - 241, The telemetry rule(s) (e.g.,
the rule with id 1111 that uses HOTSTAR_INITIATOR_DOMAINS and
TELEMETRY_RESOURCE_TYPES) incorrectly block generic redirect/navigation hosts
like "||t.co/" and "||www.google.co.in", which breaks non-telemetry flows; fix
by removing those generic domain urlFilters from telemetry rules and either (a)
narrow each urlFilter to specific telemetry collector paths/parameters for
Hotstar, or (b) move the generic domain blocks into a separate
navigation/blocking rule set (not the Disable Telemetry rules). Ensure changes
reference the rule entries that include HOTSTAR_INITIATOR_DOMAINS and
TELEMETRY_RESOURCE_TYPES so only telemetry-specific endpoints remain blocked.

Comment thread wxt.config.ts
@WINOFFRG WINOFFRG merged commit 71aa69c into master Mar 6, 2026
1 check was pending
@github-actions github-actions Bot mentioned this pull request Mar 6, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants