-
Notifications
You must be signed in to change notification settings - Fork 71
Add proposal for topFrameMatches and excludeTopFrameMatches
#802
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
Open
polywock
wants to merge
9
commits into
w3c:main
Choose a base branch
from
polywock:Proposal-Content-Script-Top-Frame-Matching
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
9 commits
Select commit
Hold shift + click to select a range
3ac34cf
Add proposal for `topFrameMatches` and `excludeTopFrameMatches`
polywock 9396eb4
Update to include missing links.
polywock a3bcfa8
Make changes based on notes by @Rob--W and @carlosjeurissen
polywock fa51bbe
Update content-script-top-frame-matching.md
polywock 5aff0ac
Apply suggestions from code review
polywock b7f2813
Update content-script-top-frame-matching.md
polywock 131fcc5
Update content-script-top-frame-matching.md
polywock 4eb25ee
Fixing typos, links.
polywock 7d53c21
Slightly simplify.
polywock File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,149 @@ | ||
|
|
||
| # Proposal: Content Script Top Frame Origin Matching | ||
|
|
||
| **Summary** | ||
|
|
||
| API proposal to allow content script registration (both static and dynamic) to be restricted based on the origin of the top-level frame using standard match patterns ([Mdn](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/Match_patterns), [Chrome Docs](https://developer.chrome.com/docs/extensions/develop/concepts/match-patterns)), enabling more intuitive and secure site blocking/allowing functionality for extensions. | ||
|
|
||
| **Document Metadata** | ||
|
|
||
| * **Author:** [Polywock](https://github.com/polywock) | ||
| * **Sponsoring Browser:** *(Seeking browser sponsorship)* | ||
| * **Status:** Draft *(Seeking feedback and browser interest)* | ||
| * **Proposal Champions:** [Dave Vandyke](https://github.com/kzar), [Carlos Jeurissen](https://github.com/carlosjeurissen), [Raymond Hill](https://github.com/gorhill), [Polywock](https://github.com/polywock) | ||
| * **Created:** 2025-03-30 | ||
| * **Related Issues:** | ||
| * [w3c/webextensions#763](https://github.com/w3c/webextensions/issues/763) | ||
| * [w3c/webextensions#117](https://github.com/w3c/webextensions/issues/117) | ||
| * [w3c/webextensions#668](https://github.com/w3c/webextensions/issues/668) | ||
| * [Chromium Issue 40202338](https://issues.chromium.org/issues/40202338) | ||
|
|
||
| ## Motivation | ||
|
|
||
| ### Objective | ||
|
|
||
| This proposal introduces a mechanism to further restrict content script injection by adding a filter based on the **origin** of the **top-level document**. This allows for more precise control over where scripts execute, while also enabling developers to create site blocklists or allowlists that better align with user expectations, improve performance, and enhance security. | ||
|
|
||
|
|
||
| ### Use Cases | ||
|
|
||
| 1. **Intuitive Site Blocking/Allowing:** Many extensions offer users the ability to disable functionality on specific websites. Currently, using frame-level exclusion rules (like `excludeMatches`) on a dynamic content script is a common approach. However, this leads to counter-intuitive behavior: | ||
| * If a user blocks `https://example.com/*` using `excludeMatches`, the extension's content script *still runs* on `https://example.com/page` if an embedded iframe loads content from a *different*, non-blocked domain (assuming `all_frames: true`). | ||
| * Conversely, if a user visits `https://anothersite.com/*` which embeds an iframe from the blocked `https://example.com/*`, the extension *is blocked* from running within that embedded `example.com` frame, even though the user likely only intended to block the extension when visiting `example.com` directly as the main page. | ||
|
|
||
| 2. **Security:** Restricting content scripts based on the top-level frame's origin enhances security. For scripts registered with `all_frames: true`, developers can ensure they only execute when the main page's origin is one they expect, preventing accidental injection into sensitive contexts or reducing the impact of potential vulnerabilities. | ||
|
|
||
| 3. **Performance:** By preventing script injection at the browser level based on top-frame origin criteria, extensions avoid the performance implications of the [current workaround](#alternatives). | ||
|
|
||
|
|
||
| ### Known Consumers | ||
| Developer interest is evident in the related GitHub and Chromium discussion ([#763](https://github.com/w3c/webextensions/issues/763), [#117](https://github.com/w3c/webextensions/issues/117), [#668](https://github.com/w3c/webextensions/issues/668), [40202338](https://issues.chromium.org/issues/40202338)). This feature addresses a common pattern (site blocking/allowing by origin) that currently requires less secure and efficient workarounds. | ||
|
|
||
| ## Specification | ||
|
|
||
| This proposal expands the definition of content scripts in both the `manifest.json` and the scripting API (`scripting.registerContentScripts`). | ||
|
|
||
| ### Schema | ||
|
|
||
| #### Manifest `content_scripts` Entry | ||
|
|
||
| The object definition within the `content_scripts` array in `manifest.json` is expanded to include two new optional properties accepting arrays of match patterns. | ||
|
|
||
| ```json5 | ||
| { | ||
| // ... existing content_script properties like "matches", "exclude_matches", etc. | ||
|
|
||
| // If specified: Only inject if the top-level frame's origin matches at least one of these patterns. | ||
| "top_frame_matches": ["MatchPattern"], | ||
|
|
||
| // If specified: Only inject if the top-level frame's origin isn't a match for any pattern. | ||
| "exclude_top_frame_matches": ["MatchPattern"] | ||
| } | ||
| ``` | ||
|
|
||
| *Where `MatchPattern` is a string containing a single match pattern. | ||
|
|
||
|
|
||
| #### `scripting.RegisteredContentScript` Type | ||
|
|
||
| The `RegisteredContentScript` type used by `scripting.registerContentScripts()` and `scripting.updateContentScripts()` is expanded similarly: | ||
|
|
||
| ```typescript | ||
| dictionary RegisteredContentScript { | ||
| // ... existing RegisteredContentScript properties like "matches", "excludeMatches", etc. | ||
|
|
||
| // If provided, only inject if the top-level frame's origin matches at least one of these patterns. | ||
| MatchPattern[]? topFrameMatches; | ||
|
|
||
| // If provided, Only inject if the top-level frame's origin isn't a match for any pattern. | ||
| MatchPattern[]? excludeTopFrameMatches; | ||
| } | ||
| ``` | ||
|
|
||
|
|
||
| ### Behavior / Implementation | ||
|
|
||
| 1. **Validation:** When processing `content_scripts` from `manifest.json` or a call to `scripting.registerContentScripts` / `scripting.updateContentScripts`: | ||
| * The browser must first validate all patterns provided in `topFrameMatches` and `excludeTopFrameMatches` as they would validate patterns provided through `matches` and `excludeMatches`. That includes validating that all provided patterns are not malformed. If malformed URL patterns are found, the browser must treat this as an error. | ||
| * Empty arrays are valid values for both `topFrameMatches` and `excludeTopFrameMatches`. | ||
| * Additionally, if any pattern contains a path component other than the wildcard path `/*` (i.e., it specifies a specific path like `/foo` or `/bar/*`), the browser must treat this as an error. Patterns without an explicit path or those explicitly using `/*` are considered valid. This restriction ensures these patterns are intended to match origins. | ||
| * Handling validation errors: | ||
| * For static declarations in `manifest.json`, validation errors should result in a manifest parsing error, preventing the extension from loading. | ||
| * For dynamic API calls (`registerContentScripts`, `updateContentScripts`), validation errors results in the promise being rejected with an with an appropriate error (e.g., `Match patterns for top_frame_matches must not specify a path.` or `One or more match patterns in top_frame_matches weren't able to be parsed`). | ||
|
|
||
| 3. **Injection Logic:** Assuming validation passes, a content script will be injected into a frame if and only if *all* the following conditions are met: | ||
| * All existing checks based on the frame's own URL and context are satisfied (e.g., `matches`, `excludeMatches`). | ||
| * And if `topFrameMatches` was specified, the **top-level document's origin** must match at least one pattern in `topFrameMatches`. If `topFrameMatches` is an empty array, the content script will effectively never run. | ||
| * And if `excludeTopFrameMatches` was specified, the **top-level document's origin** must *not* match any pattern in `excludeTopFrameMatches`. | ||
|
|
||
|
|
||
|
|
||
| The **Top-level document's origin** is determined as follows: | ||
|
|
||
| 1. First, obtain the "URL for matching" for the top-level document by applying the "Determine the URL for matching a document" algorithm, as specified in the W3C WebExtensions specification ([section 18.1](https://w3c.github.io/webextensions/specification/index.html#determine-the-url-for-matching-a-document)). The `match_origin_as_fallback` parameter of this algorithm must be interpreted as `true`. | ||
|
|
||
| 2. If the W3C algorithm returns a "URL for matching": | ||
| * This URL is then canonicalized to its origin part for the purpose of this matching. This means retaining the scheme and authority (hostname and port, if specified or non-default), while any path, query, or fragment components are discarded. | ||
| * The resulting string is the "top-level document's origin" that is compared against the patterns in `top_frame_matches` and `exclude_top_frame_matches`. | ||
|
|
||
| **Handling Undeterminable Origins for Matching** | ||
|
|
||
| If the top-level document’s origin cannot be determined and either `topFrameMatches` or `excludeTopFrameMatches` is specified, the browser MUST NOT inject the content script. This prevents accidental execution in ambiguous or sensitive contexts. | ||
|
|
||
| ### New Permissions | ||
|
|
||
| No new permissions are required. The `topFrameMatches` and `excludeTopFrameMatches` properties only serve to *restrict* where a content script can run, based on the host permissions already requested by the `matches` property. Existing host permission warnings remain appropriate and sufficient. | ||
|
|
||
|
|
||
| ## Security and Privacy | ||
|
|
||
| ### Exposed Sensitive Data | ||
|
|
||
| This API does not expose any new data to the extension. It uses the top-level document's origin which is generally less specific than the full URL and already implicitly available to content scripts running within frames of that top-level document. | ||
|
|
||
| ### Additional Security Considerations | ||
|
|
||
| This feature enhances the principle of least privilege by allowing developers to be more specific about the top-level origins in which their scripts should operate. | ||
|
|
||
| ## Alternatives | ||
|
|
||
| ### Existing Workarounds | ||
|
|
||
| Developers can achieve similar *behavior* (but not with same performance or security) by: | ||
|
|
||
| 1. Registering a content script with broad `matches` (e.g., `<all_urls>`). | ||
| 2. Inside the content script, determine the top-level frame's origin `location.ancestorOrigins` or by messaging the background script. | ||
| 3. Asynchronously fetch the user's blocklist/allowlist (likely stored by origin) from `browser.storage`. | ||
| 4. Compare the top-level origin against the list. | ||
| 5. If the origin is blocked (or not allowed), exit early. | ||
|
|
||
| **Limitations of Workarounds:** | ||
|
|
||
| 1. **Inefficiency:** The content script still must be injected, potentially across dozens or hundreds of tabs. Even though it exits immediately without further logic, the effect of having these scripts loaded may have significant performance implications. | ||
| 3. **Asynchronous:** Checking `browser.storage` is asynchronous. Scripts needing synchronous initialization (e.g., modifying the DOM early via `run_at: document_start`) cannot reliably block themselves before potentially executing some code. | ||
| 4. **Attack Surface:** The content script still must be injected, potentially in sensitive sites that the user intended to block. Vulnerabilities in the script or its dependencies could theoretically be exploited. | ||
| 5. **Conflicts:** The mere act of injecting a script (especially via the MAIN content script world) can cause conflicts with website code that are often hard to diagnose, potentially due to factors outside an extension developer's immediate control like polyfills, bundler runtime code, etc. | ||
|
|
||
| ### Open Web API | ||
|
|
||
| N/A. *This feature is specific to the WebExtensions model.* | ||
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.