Xerillio
Xerillio

Reputation: 5259

Firefox extension background script terminated preemptively

I have a Firefox extension that for certain pages listens to the webRequest.onHeadersReceived for specific URLs that it opens in order to remove the "content-disposition" header when it contains "attachment". That way some content is shown in the browser rather than downloaded as a file.

When the extension opens the URL in a new tab, it works fine; the listener is called and I can modify the headers. But, if I instead open the URL in the currently active tab, it seems the background script gets terminated and the listener is never called.

Is there a way to prevent the background script from terminating, or am I doing something wrong to cause this?

From a content script a message is sent containing a URL, e.g. for an image, to open. The background script:

// background.ts

browser.runtime.onMessage.addListener(handleMessage);

function handleMessage(message: IMessage, sender: browser.runtime.MessageSender, sendResponse: (response?: any) => void) {
    const command = message.command;
    switch (command) {
        case "OPEN_IMAGE": {
            const { url, focusTab, newTab } = message.data;
            return openImage(url, focusTab, newTab);
        }
        // other cases...
    }
}

async function openImage(url: string, shouldFocus: boolean, newTab: boolean) {
    let tab: browser.tabs.Tab;

    if (newTab) {
        // This branch works as expected. The URL is loaded (below) and the image
        // is shown rather than downloaded as a file.
        tab = await browser.tabs.create({ active: shouldFocus });
    }
    else {
        // Using this tab to load the URL into doesn't work. The image is downloaded
        // as a file, rather than shown in the tab.
        const activeTabs = await browser.tabs.query({ active: true, currentWindow: true });
        tab = activeTabs[0];
    }

    requestHandler.startListening(url);
    await browser.tabs.update(tab.id!, { url, loadReplace: newTab });
}

Inside requestHandler.startListening this is what happens:

class RequestHandler {
    private readonly _urlsToListenFor: string[] = [];

    public startListening = (url: string) => {
        console.log("added");
        this._urlsToListenFor.push(url);

        // I've already checked no listener is added before this
        browser.webRequest.onHeadersReceived.addListener(
            this.onHeadersReceived,
            {
                urls: ["*://*/*"],
                types: ["main_frame"]
            },
            ["responseHeaders", "blocking"]);
    };

    private onHeadersReceived = (details: browser.webRequest._OnHeadersReceivedDetails) => {
        const listenerIndex = this._urlsToListenFor.indexOf(details.url);
        if (listenerIndex === -1) return;
    
        console.log("removed");
        this._urlsToListenFor.splice(listenerIndex, 1);
    
        return this.getBlockingResponse(details);
    };

    private getBlockingResponse = (details: browser.webRequest._OnHeadersReceivedDetails) => {
        // Make sure the image is opened in the browser rather than downloaded
        const contentDispIndex = details.responseHeaders
            ?.findIndex(h => h.name.toLowerCase() === "content-disposition" &&
                             h.value!.toLowerCase().startsWith("attachment"));
    
        return {
            responseHeaders: details.responseHeaders!
                .filter((_, index) => index !== contentDispIndex)
        };
    };
}

console.log("new RequestHandler");
export default new RequestHandler();

Given the console.log statements, this is what I see in the console:

// newTab === true
added
removed

// newTab === false
added
new RequestHandler

Upvotes: 1

Views: 66

Answers (0)

Related Questions