Skip to content

Proposal: Add topFrameMatches / excludeTopFrameMatches #763

@polywock

Description

@polywock

Many extensions offer, or would like to offer, users the ability to disable the extension on certain websites. But currently, the easiest and safest method of implementing that is by using a dynamic content script and adding the blocked sites to excludeMatches, but this results in behavior that doesn't align with user expectations.

When a user blocks a site like Reddit, they expect the extension to be completely disabled when they visit Reddit. However, with the usual excludeMatches approach: the content script still runs on Reddit if an embedded frame loads content from a different domain. Conversely, if a user visits another website that embeds Reddit content (e.g., a news site displaying a Reddit post), the extension is blocked from running within that embedded Reddit frame, even if the user did not intend to block it there.

Proposal

Content script registration should accept topFrameMatches / excludeTopFrameMatches. If provided, it will further restrict a content script based on the top frame's URL.

This allows extensions to, more easily and safely, implement a blocklist/allowlist system that's user intuitive.

Usage Example: Dark theme on all websites/frames unless it's on Reddit.

{
   "matches": ["https://*/*"],
   "exclude_top_frame_matches": ["https://www.reddit.com/*"],
   "all_frames": true,
   "js": ["force_dark_theme.js"]
}

Permission Warnings

Since this feature only restricts where a content script runs and does not expand its scope, the existing host permission warnings remain appropriate. Unlike matches, using top_frame_matches does not request any additional host access.

Security use case

As highlighted in @carlosjeurissen's proposal (#117), allowing developers to restrict content scripts based on the main frame’s origin reduces the overall attack surface. This ensures scripts injected with all_frames: true only execute if the main frame matches specific origins, preventing unintended interactions or vulnerabilities from scripts running in unexpected contexts.

Current workaround limitations

You can achieve similar functionality by having the content script exit early after loading. In this approach, the script initially loads on every page, retrieves the user's blocked website list from browser.storage, and then checks the main frame's URL (e.g., via Location.ancestorOrigins) to determine whether it should continue running. However, this method comes with several drawbacks:

  1. Inefficiency: The content script must still load, potentially across dozens or hundreds of open tabs. Even though it exits immediately without further logic, the effect of having these scripts loaded may have significant performance implications.
  2. Asynchronous: Accessing the user's blocklist from browser.storage is asynchronous, making it unsuitable for extensions that rely on synchronous initialization.
  3. Attack surface: If a user adds a sensitive website (e.g., a bank) to the blocklist, the content script should ideally never load on that site. Minimizing the extension’s footprint is always preferable. Even if the extension isn’t designed to be harmful, unintended vulnerabilities or bad code could still pose a risk.
  4. Conflicts: Even if a script exits early, merely loading it can still lead to conflicts or unintended interactions with certain websites. Such conflicts might arise from polyfills, bundlers, or other underlying factors that developers may not necessarily be aware of. Such conflicts can be particularly challenging to diagnose and resolve.

Related Proposals

First proposed by @kzar and @carlosjeurissen.

Issues #117 and #668 were closed in favor of this proposal, but further discussion and additional use cases can be found in their respective threads.

Relevant Chromium issue: https://issues.chromium.org/issues/40202338

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions