Merge pull request #5235 from tridactyl/better-webrequest-autocmds

Better webrequest autocmds
This commit is contained in:
Oliver Blanthorn 2026-02-14 11:38:56 +00:00 committed by GitHub
commit dc18d58edc
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 57 additions and 55 deletions

View file

@ -195,27 +195,6 @@ for (const requestEvent of webrequests.requestEvents) {
})
}
config.addChangeListener("autocmds", (previous, current) =>
webrequests.requestEvents.forEach(
requestEvent =>
// If there are autocmd(s) for this requestEvent
current[requestEvent] !== undefined &&
Object.entries(
current[requestEvent] as Record<string, string>,
).forEach(([pattern, func]) => {
// R.path returns undefined if any part of the path is missing rather than saying "computer says no"
const path = R.path([requestEvent, pattern])
// If this is a new autocmd, register it
path(current) !== path(previous) &&
webrequests.registerWebRequestAutocmd(
requestEvent,
pattern,
func,
)
}),
),
)
// }}}

View file

@ -15,7 +15,7 @@ export const requestEvents = Object.keys(requestEventExpraInfoSpecMap)
// eslint-disable-next-line @typescript-eslint/ban-types
export const LISTENERS: Record<string, Record<string, Function>> = {}
export const registerWebRequestAutocmd = (
export const registerWebRequestAutocmd = async (
requestEvent: string,
pattern: string,
func: string,
@ -23,16 +23,30 @@ export const registerWebRequestAutocmd = (
// I'm being lazy - strictly the functions map strings to void | blocking responses
// eslint-disable-next-line @typescript-eslint/ban-types
const listener = eval(func) as Function
if (!LISTENERS[requestEvent]) LISTENERS[requestEvent] = {}
LISTENERS[requestEvent][pattern] = listener
return browser.webRequest["on" + requestEvent].addListener(
await browser.webRequest["on" + requestEvent].addListener(
listener,
{ urls: [pattern] },
requestEventExpraInfoSpecMap[requestEvent],
)
const oldListener = LISTENERS[requestEvent][pattern];
// Add the new listener to our list if everything was successful
LISTENERS[requestEvent][pattern] = listener
// Remove any previously registered autocmd for the same pattern
if (oldListener) {
await browser.webRequest["on" + requestEvent].removeListener(
oldListener
)
}
}
export const unregisterWebRequestAutocmd = (requestEvent, pattern) =>
browser.webRequest["on" + requestEvent].removeListener(
LISTENERS[requestEvent][pattern],
)
export const unregisterWebRequestAutocmd = async (requestEvent, pattern) => {
if (LISTENERS[requestEvent] && LISTENERS[requestEvent][pattern]) {
await browser.webRequest["on" + requestEvent].removeListener(
LISTENERS[requestEvent][pattern],
)
}
}

View file

@ -4725,29 +4725,36 @@ export function getAutocmdEvents() {
- DocEnd: When a webpage unloaded/closed or backward/forward in history. Exactly, the [pagehide event](https://developer.mozilla.org/en-US/docs/Web/API/Window/pagehide_event).
- TabEnter: When a tab get focus.
- TabLeft: When a tab lost focus or closed.
- A supported webRequest event (AuthRequired, BeforeRedirect, BeforeRequest, BeforeSendHeaders, Completed, ErrorOccured, HeadersReceived, ResponseStarted and SendHeaders): the corresponding [WebExtension webRequest event](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/webRequest#Events)
- The 'HistoryState' event is triggered when a page uses the web history API to change the page location / URI. It should be used in preference to 'UriChange' below since it will use almost no resources. The 'UriChange' event may work on websites where 'HistoryState' does not.
- The 'HistoryPushState' is triggered only when a page calls 'history.pushState' to change URI, and 'HistoryReplace' is for 'history.replace'. By the way, the HistoryPopState is not implemented.
- The 'UriChange' event is for "single page applications" which change their URIs without triggering DocStart or DocLoad events. It uses a timer to check whether the URI has changed, which has a small impact on battery life on pages matching the `url` parameter. We suggest using it sparingly.
*
* The 'HistoryState' event is triggered when a page uses the web history API to change the page location / URI. It should be used in preference to 'UriChange' below since it will use almost no resources. The 'UriChange' event may work on websites where 'HistoryState' does not.
* @param url type depends on the event
*
* The 'HistoryPushState' is triggered only when a page call 'history.pushState' to change URI, and 'HistoryReplace' is for 'history.replace'. By the way, the HistoryPopState is not implemented.
- For most events (DocStart, DocEnd, TabEnter, TabLeft, ...): a JavaScript regex (e.g. `www\.amazon\.co.*`)
- We just use [URL.search](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/search)
- For TriStart: regular expression that matches the hostname of the computer the autocmd should be run on. This requires the native messenger to be installed, except for the ".*" regular expression which will always be triggered, even without the native messenger.
- For webRequest events (AuthRequired, BeforeRedirect, BeforeRequest, BeforeSendHeaders, Completed, ErrorOccured, HeadersReceived, ResponseStarted and SendHeaders): a [URL match pattern](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/Match_patterns)
*
* The 'UriChange' event is for "single page applications" which change their URIs without triggering DocStart or DocLoad events. It uses a timer to check whether the URI has changed, which has a small impact on battery life on pages matching the `url` parameter. We suggest using it sparingly.
*
* @param url For DocStart, DocEnd, TabEnter, and TabLeft: a JavaScript regex (e.g. `www\.amazon\.co.*`)
*
* We just use [URL.search](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/search).
*
* For TriStart: A regular expression that matches the hostname of the computer
* the autocmd should be run on. This requires the native messenger to be
* installed, except for the ".*" regular expression which will always be
* triggered, even without the native messenger.
*
* For AuthRequired, BeforeRedirect, BeforeRequest, BeforeSendHeaders, Completed, ErrorOccured, HeadersReceived, ResponseStarted and SendHeaders, a [URL match pattern](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/Match_patterns)
*
* @param excmd The excmd to run (use [[composite]] to run multiple commands), __except__ for AuthRequired, BeforeRedirect, BeforeRequest, BeforeSendHeaders, Completed, ErrorOccured, HeadersReceived, ResponseStarted and SendHeaders, events where it must be an inline JavaScript function which maps [details objects specific to the event](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/webRequest#Events) to [blocking responses](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/webRequest/BlockingResponse). This JavaScript function will run in the background context.
*
* For example: `autocmd BeforeRequest https://www.bbc.co.uk/* () => ({redirectUrl: "https://old.reddit.com"})`. Note the brackets which ensure JavaScript returns a blocking response object rather than interpreting it as a block statement.
*
* For DocStart, DocLoad, DocEnd, TabEnter, TabLeft, FullscreenEnter, FullscreenLeft, FullscreenChange and UriChange: magic variables are available which are replaced with the relevant string at runtime:
* @param command type depends on the event
- For most events (DocStart, DocEnd, TabEnter, TabLeft, ...): the excmd to run (use [[composite]] to run multiple commands).
- Example for zooming in more on a website:
```
autocmd DocStart .*example\.com.* zoom 150 false TRI_FIRED_MOZ_TABID
```
- For webRequest events (AuthRequired, BeforeRedirect, BeforeRequest, BeforeSendHeaders, Completed, ErrorOccured, HeadersReceived, ResponseStarted and SendHeaders): the text of a javascript function that should accept a [details objects specific to the event](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/webRequest#Events) and return a [blocking response](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/webRequest/BlockingResponse). This JavaScript function will run in the background context.
- Example for redirecting from new to old reddit:
```
autocmd BeforeRequest https://www.reddit.com/r/* (details) => ({redirectUrl: details.url.replace(/^https:\/\/www\./, "https://old.")})
```
* For non-webRequest events, magic variables are available which are replaced with the relevant string at runtime:
- `TRI_FIRED_MOZ_TABID`: Provides Mozilla's `tabID` associated with the fired event.
- `TRI_FIRED_TRI_TABINDEX`: Provides tridactyls internal tab index associated with the fired event.
- `TRI_FIRED_MOZ_WINID`: Provides Mozilla's `windowId` associated with the fired event.
@ -4765,18 +4772,20 @@ export function getAutocmdEvents() {
- `TRI_FIRED_PINNED`: Whether the tab is pinned.
- `TRI_FIRED_TITLE`: The title of the tab.
- `TRI_FIRED_URL`: The URL of the document that the tab is displaying.
*
* For example: `autocmd DocStart .*example\.com.* zoom 150 false TRI_FIRED_MOZ_TABID`.
*
* For debugging, use `:set logging.autocmds debug` and check the Firefox web console. `WebRequest` events have no logging.
*
*/
//#background
export function autocmd(event: string, url: string, ...excmd: string[]) {
export async function autocmd(event: string, url: string, ...excmd: string[]) {
// rudimentary run time type checking
// TODO: Decide on autocmd event names
if (!getAutocmdEvents().includes(event)) throw new Error(event + " is not a supported event.")
return config.set("autocmds", event, url, excmd.join(" "))
if (!getAutocmdEvents().includes(event)) {
throw new Error(event + " is not a supported event.");
}
if (webrequests.requestEvents.includes(event)) {
await webrequests.registerWebRequestAutocmd(event, url, excmd.join(" "));
}
return config.set("autocmds", event, url, excmd.join(" "));
}
/**