Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Proposal: Add mainFrameMatches / excludeMainFrameMatches #763

Open
polywock opened this issue Feb 11, 2025 · 11 comments
Open

Proposal: Add mainFrameMatches / excludeMainFrameMatches #763

polywock opened this issue Feb 11, 2025 · 11 comments
Labels
needs-triage: chrome Chrome needs to assess this issue for the first time needs-triage: firefox Firefox needs to assess this issue for the first time needs-triage: safari Safari needs to assess this issue for the first time

Comments

@polywock
Copy link

polywock commented Feb 11, 2025

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 mainFrameMatches / excludeMainFrameMatches, and for consistency, Glob versions of each. 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_main_frame_matches": ["https://*.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 main_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 : https://issues.chromium.org/issues/40202338
Thanks @ghostwords for pointing it out.

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.

@github-actions github-actions bot added needs-triage: chrome Chrome needs to assess this issue for the first time needs-triage: firefox Firefox needs to assess this issue for the first time needs-triage: safari Safari needs to assess this issue for the first time labels Feb 11, 2025
@polywock polywock changed the title Proposal: Add excludeTopLevelMatches and excludeTopLevelGlobs. Proposal: Add excludeTopFrameMatches and excludeTopFrameGlobs. Feb 11, 2025
@gorhill
Copy link

gorhill commented Feb 11, 2025

Related issue: #668

@ghostwords
Copy link

Chromium bug: https://issues.chromium.org/issues/40202338

@polywock
Copy link
Author

Another related issue #117.

@polywock
Copy link
Author

polywock commented Feb 15, 2025

@Rob--W My concerns with the location.ancestorOrigins approach...

  1. The content script still must load and that might have memory or other performance implications. Particularly for power users with large amount of tabs.
  2. If the user adds a sensitive website (bank, etc) to the blocklist, it would be ideal if the content script never loads in the first place. A lower footprint is always better. Even if the extension is not intentionally being nefarious, bad code can always enter the system (e.g. an NPM package being hacked, etc).
  3. In certain cases just loading a content script, even if exiting early, could result in conflicts or issues with a website. I've had that problem a few times with polyfills.

I've updated my proposal to include these points.

@ghostwords
Copy link

ghostwords commented Feb 18, 2025

Furthermore, as @gorhill already pointed out in #668 (comment), how would static document_start main world scripts know what to compare location.ancestorOrigins to? We don't have dom.execute() yet, and the suggested hacks are not reasonable (too complex, almost certainly come with pitfalls/downsides/caveats).

We're asking for a simple API that does what we want, without requiring each and every developer to implement hacks or unnecessary/bug prone custom matching logic.

@polywock
Copy link
Author

polywock commented Feb 19, 2025

@ghostwords Not sure about that context, but In my use case, it can be used to disable the extension based on the top frame's origin. E.g. the user adds reddit.com to a list of websites they want disabled, the extension checks location.ancestorOrigin on every subframe to confirm the top frame's origin isn't reddit.com. Although, I do think that it would be much safer, performative and bug free as a declarative API.

@oliverdunk
Copy link
Member

To reiterate what I said in the last public meeting, I'm personally (not speaking on behalf of Chrome) supportive of this request. We would want to restrict matching to be based on the origin (or we could use a match pattern with a forced-wildcard path). Matching based on an actual path is much harder from a technical perspective.

There is already a lot of precedence for a "disable this site" option in the extension ecosystem, so I don't feel as strongly that we need to prove the demand for this.

In parallel, I do also like @Rob--W's suggestion in #668 to solve this with content script params. It wouldn't avoid injecting a content script entirely, so there is still value in this request, but it definitely seems like it would be an improvement and would be worth pursuing in parallel. I know that proposal hasn't moved anywhere in a long time, but chatting with Rob recently I think we are both keen to see if we can get it some momentum again.

@carlosjeurissen
Copy link
Contributor

carlosjeurissen commented Mar 5, 2025

@hackademix @ghostwords @polywock would ancestorMatches/excludeAncestorMatches cover all your use-cases? If so, we can proceed discussion in #668. Edit: it does not, see #763 (comment)

@Rob--W @xeenon is using matches for ancestor frames doable implementation wise in Firefox/Safari?

@Rob--W Would Mozilla consider supporting location.ancestorOrigins specifically for extensions only which would address some of the concerns for offering this API? See mozilla/standards-positions#466

@oliverdunk As mentioned in #668 (comment), having a way to filter ancestorMatches is very useful for CSS injections as you can not check for location.ancestorOrigins.

As to not have to add two properties (ancestorMatches and excludeAncestorMatches) or potentially four (with topFrameMatches and excludeTopFrameMatches), we may want to consider the exclude match patterns as proposed in #756

In any case, would want to propose a new <extension_urls> matches keyword to be used for matching the own extension URLs. This would be useful to restrict scripting to frames embedded in extensions specifically. Addressing #117

@polywock
Copy link
Author

polywock commented Mar 5, 2025

@carlosjeurissen I appreciate the suggestion, but the other proposal doesn't address my use case. It would be best to keep these as separate proposals.

@ghostwords
Copy link

location.ancestorOrigins is not enough: #763 (comment)

When a user disables the extension on a site, we want to stop injecting content scripts into that site and all of its frames when the site is visited directly, but not when the site is a frame on another site. That's what this issue is about, I think. Will this other API idea let us do that? If yes, great!

@carlosjeurissen
Copy link
Contributor

@polywock @ghostwords thanks for clarifying!

As a nitpick for this proposal. Going for mainFrameMatches and excludeMainFrameMatches would match the sub/main frame terminology used in extensions.

@polywock polywock changed the title Proposal: Add excludeTopFrameMatches and excludeTopFrameGlobs. Proposal: Add excludeMainFrameMatches and excludeMainFrameGlobs. Mar 6, 2025
@polywock polywock changed the title Proposal: Add excludeMainFrameMatches and excludeMainFrameGlobs. Proposal: Add mainFrameMatches and excludeMainFrameMatches. Mar 6, 2025
@polywock polywock changed the title Proposal: Add mainFrameMatches and excludeMainFrameMatches. Proposal: Add mainFrameMatches / excludeMainFrameMatches Mar 6, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
needs-triage: chrome Chrome needs to assess this issue for the first time needs-triage: firefox Firefox needs to assess this issue for the first time needs-triage: safari Safari needs to assess this issue for the first time
Projects
None yet
Development

No branches or pull requests

5 participants